From a1a89aa36cde199817872f7bd3443f0f2ea2b72f Mon Sep 17 00:00:00 2001 From: Valentin Rocher Date: Thu, 24 Feb 2022 11:11:07 +0100 Subject: [PATCH 1/5] make source module and stream utils --- .../kotlin/com/akuleshov7/ktoml/Toml.kt | 77 +++++- .../akuleshov7/ktoml/parsers/TomlParser.kt | 116 ++++++++-- ktoml-file/build.gradle.kts | 1 + .../com/akuleshov7/ktoml/file/FileUtils.kt | 19 +- .../akuleshov7/ktoml/file/TomlFileReader.kt | 49 +++- .../ktoml/file/TomlFileParserTest.kt | 32 ++- ktoml-source/build.gradle.kts | 90 +++++++ .../akuleshov7/ktoml/source/SourceUtils.kt | 14 ++ .../ktoml/source/TomlSourceReader.kt | 96 ++++++++ .../ktoml/file/TomlSourceParserTest.kt | 75 ++++++ .../com/akuleshov7/ktoml/source/JvmStreams.kt | 69 ++++++ .../akuleshov7/ktoml/source/StreamTests.kt | 219 ++++++++++++++++++ .../ktoml/source/class_cast_regression1.toml | 4 + .../ktoml/source/class_cast_regression2.toml | 4 + .../ktoml/source/complex_toml_tables.toml | 8 + .../ktoml/source/partial_decoder.toml | 7 + .../source/partial_parser_regression.toml | 12 + .../ktoml/source/simple_example.toml | 14 ++ .../ktoml/source/toml_tables_order.toml | 8 + settings.gradle.kts | 1 + 20 files changed, 859 insertions(+), 56 deletions(-) create mode 100644 ktoml-source/build.gradle.kts create mode 100644 ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/SourceUtils.kt create mode 100644 ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt create mode 100644 ktoml-source/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlSourceParserTest.kt create mode 100644 ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt create mode 100644 ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt create mode 100644 ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/class_cast_regression1.toml create mode 100644 ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/class_cast_regression2.toml create mode 100644 ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/complex_toml_tables.toml create mode 100644 ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/partial_decoder.toml create mode 100644 ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/partial_parser_regression.toml create mode 100644 ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/simple_example.toml create mode 100644 ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/toml_tables_order.toml 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 69178761..32827f8a 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt @@ -65,6 +65,22 @@ public open class Toml( deserializer: DeserializationStrategy, toml: List, config: TomlConfig + ): T { + return decodeFromString(deserializer, toml.asSequence(), config) + } + + /** + * simple deserializer of a sequence of strings in a toml format + * + * @param toml sequence with strings in toml format + * @param deserializer deserialization strategy + * @param config + * @return deserialized object of type T + */ + public fun decodeFromString( + deserializer: DeserializationStrategy, + toml: Sequence, + config: TomlConfig = this.config ): T { val parsedToml = tomlParser.parseStringsToTomlTree(toml, config) return TomlMainDecoder.decode(deserializer, parsedToml, this.config) @@ -94,6 +110,30 @@ public open class Toml( return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config) } + /** + * partial deserializer of a sequence of lines in a toml format. + * Will deserialize only the part presented under the tomlTableName table. + * If such table is missing in he input - will throw an exception + * + * (!) Useful when you would like to deserialize only ONE table + * and you do not want to reproduce whole object structure in the code + * + * @param deserializer deserialization strategy + * @param tomlLines sequence of TOML lines + * @param tomlTableName fully qualified name of the toml table (it should be the full name - a.b.c.d) + * @param config + * @return deserialized object of type T + */ + public fun partiallyDecodeFromLines( + deserializer: DeserializationStrategy, + tomlLines: Sequence, + tomlTableName: String, + config: TomlConfig = TomlConfig() + ): T { + val fakeFileNode = generateFakeTomlStructureForPartialParsing(tomlLines, tomlTableName, config, TomlParser::parseLines) + return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config) + } + /** * partial deserializer of a string in a toml format (separated by newlines). * Will deserialize only the part presented under the tomlTableName table. @@ -123,15 +163,44 @@ public open class Toml( return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config) } + /** + * partial deserializer of a sequence of lines in a toml format. + * Will deserialize only the part presented under the tomlTableName table. + * If such table is missing in he input - will throw an exception + * + * (!) Useful when you would like to deserialize only ONE table + * and you do not want to reproduce whole object structure in the code + * + * @param deserializer deserialization strategy + * @param tomlLines sequence of strings with toml input + * @param tomlTableName fully qualified name of the toml table (it should be the full name - a.b.c.d) + * @param config + * @return deserialized object of type T + */ + public fun partiallyDecodeFromString( + deserializer: DeserializationStrategy, + tomlLines: Sequence, + tomlTableName: String, + config: TomlConfig = TomlConfig() + ): T { + val fakeFileNode = generateFakeTomlStructureForPartialParsing( + tomlLines, + tomlTableName, + config, + TomlParser::parseLines, + ) + return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config) + } + // ================== other =============== @Suppress("TYPE_ALIAS") - private fun generateFakeTomlStructureForPartialParsing( - toml: String, + private fun generateFakeTomlStructureForPartialParsing( + tomlInput: I, tomlTableName: String, config: TomlConfig = TomlConfig(), - parsingFunction: (TomlParser, String) -> TomlFile + parsingFunction: (TomlParser, I) -> TomlFile ): TomlFile { - val tomlFile = parsingFunction(TomlParser(this.config), toml) + val tomlFile = parsingFunction(TomlParser(this.config), tomlInput) val parsedToml = findPrimitiveTableInAstByName(listOf(tomlFile), tomlTableName) ?: throw MissingRequiredPropertyException( "Cannot find table with name <$tomlTableName> in the toml input. " + 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 c7c7bf4a..de200b11 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 @@ -10,6 +10,7 @@ import kotlin.jvm.JvmInline @JvmInline @Suppress("WRONG_MULTIPLE_MODIFIERS_ORDER") public value class TomlParser(private val config: TomlConfig) { + /** * Method for parsing of TOML string (this string should be split with newlines \n or \r\n) * @@ -18,8 +19,18 @@ public value class TomlParser(private val config: TomlConfig) { */ public fun parseString(toml: String): TomlFile { // It looks like we need this hack to process line separator properly, as we don't have System.lineSeparator() - val tomlString = toml.replace("\r\n", "\n") - return parseStringsToTomlTree(tomlString.split("\n"), config) + return parseLines(toml.replace("\r\n", "\n").splitToSequence("\n")) + } + + /** + * Method for parsing of TOML lines + * + * @param tomlLines toml lines + * @return the root TomlFile node of the Tree that we have built after parsing + */ + public fun parseLines(tomlLines: Sequence): TomlFile { + // It looks like we need this hack to process line separator properly, as we don't have System.lineSeparator() + return parseStringsToTomlTree(tomlLines, config) } /** @@ -30,17 +41,32 @@ public value class TomlParser(private val config: TomlConfig) { * @return the root node of the resulted toml tree * @throws InternalAstException - if toml node does not inherit TomlNode class */ - @Suppress("TOO_LONG_FUNCTION") public fun parseStringsToTomlTree(tomlLines: List, config: TomlConfig): TomlFile { + return parseStringsToTomlTree(tomlLines.asSequence(), config) + } + + /** + * Parsing the list of strings to the TOML intermediate representation (TOML- abstract syntax tree). + * + * @param tomlLines list with toml strings (line by line) + * @param config + * @return the root node of the resulted toml tree + * @throws InternalAstException - if toml node does not inherit TomlNode class + */ + @Suppress("TOO_LONG_FUNCTION") + public fun parseStringsToTomlTree(tomlLines: Sequence, config: TomlConfig): TomlFile { var currentParentalNode: TomlNode = TomlFile(config) // link to the head of the tree val tomlFileHead = currentParentalNode as TomlFile // need to trim empty lines BEFORE the start of processing - val mutableTomlLines = tomlLines.toMutableList().trimEmptyLines() + val trimmedTomlLines = tomlLines.trimEmptyLines() // here we always store the bucket of the latest created array of tables var latestCreatedBucket: TomlArrayOfTablesElement? = null - mutableTomlLines.forEachIndexed { index, line -> + var index = 0 + val linesIterator = trimmedTomlLines.iterator() + while (linesIterator.hasNext()) { + val line = linesIterator.next() val lineNo = index + 1 // comments and empty lines can easily be ignored in the TomlTree, but we cannot filter them out in mutableTomlLines // because we need to calculate and save lineNo @@ -65,7 +91,7 @@ public value class TomlParser(private val config: TomlConfig) { val tableSection = TomlTablePrimitive(line, lineNo, config) // if the table is the last line in toml, then it has no children, and we need to // add at least fake node as a child - if (index == mutableTomlLines.lastIndex) { + if (!linesIterator.hasNext()) { tableSection.appendChild(TomlStubEmptyNode(lineNo, config)) } // covering the case when the processed table does not contain nor key-value pairs neither tables (after our insertion) @@ -94,7 +120,9 @@ public value class TomlParser(private val config: TomlConfig) { } } } + index++ } + return tomlFileHead } @@ -104,18 +132,74 @@ public value class TomlParser(private val config: TomlConfig) { } } - private fun MutableList.trimEmptyLines(): MutableList { - if (this.isEmpty()) { - return this - } - // removing all empty lines at the end, to cover empty tables properly - while (this.last().isEmptyLine()) { - this.removeLast() - if (this.isEmpty()) { - return this + // This code is eavily inspired by the TransformingSequence code in kotlin-lib + private fun Sequence.trimEmptyLines(): Sequence { + return object : Sequence { + + override fun iterator(): Iterator { + return object : Iterator { + private val linesIterator = this@trimEmptyLines.iterator() + + // -1 for unknown, 0 for done, 1 for empty lines, 2 for continue + private var nextState: Int = -1 + private var nextItem: String? = null + private var nextRealItem: String? = null + private val emptyLinesBuffer: MutableList = mutableListOf() + + private fun calcNext() { + if (emptyLinesBuffer.isNotEmpty()) { + nextState = 1 + nextItem = emptyLinesBuffer.removeFirst() + return + } + if (nextRealItem != null) { + nextState = 2 + nextItem = nextRealItem + nextRealItem = null + return + } + while (linesIterator.hasNext()) { + val line = linesIterator.next() + if (line.isEmptyLine()) { + emptyLinesBuffer.add(line) + } else { + nextRealItem = line + if (emptyLinesBuffer.isEmpty()) { + nextState = 2 + nextItem = nextRealItem + nextRealItem = null + } else { + nextState = 1 + nextItem = emptyLinesBuffer.removeFirst() + } + return + } + } + nextState = 0 + } + + override fun hasNext(): Boolean { + if (nextState == -1) { + calcNext() + } + return nextState == 1 || nextState == 2 + } + + override fun next(): String { + if (nextState == -1) { + calcNext() + } + if (nextState == 0) { + throw NoSuchElementException() + } + val result = nextItem + nextItem = null + nextState = -1 + return result as String + } + } } } - return this } private fun String.isArrayOfTables(): Boolean = this.trim().startsWith("[[") diff --git a/ktoml-file/build.gradle.kts b/ktoml-file/build.gradle.kts index 2a93d6e3..f09bbb09 100644 --- a/ktoml-file/build.gradle.kts +++ b/ktoml-file/build.gradle.kts @@ -58,6 +58,7 @@ kotlin { dependencies { implementation("com.squareup.okio:okio:${Versions.OKIO}") implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") + implementation(project(":ktoml-source")) implementation(project(":ktoml-core")) } } 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 85d14a50..101f9e00 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,28 +4,19 @@ package com.akuleshov7.ktoml.file -import okio.BufferedSink -import okio.FileNotFoundException -import okio.FileSystem +import okio.* import okio.Path.Companion.toPath -import okio.buffer /** - * Simple file reading with okio (returning a list with strings) + * Uses okio to get the source from a path * - * @param tomlFile string with a path to a file - * @return list with strings * @throws FileNotFoundException if the toml file is missing */ -internal fun readAndParseFile(tomlFile: String): List { +internal fun getFileSource(filePath: String): Source { try { - val tomlPath = tomlFile.toPath() - return getOsSpecificFileSystem().read(tomlPath) { - // FixMe: may be we need to read and at the same time parse (to make it parallel) - generateSequence { readUtf8Line() }.toList() - } + return getOsSpecificFileSystem().source(filePath.toPath()) } catch (e: FileNotFoundException) { - throw FileNotFoundException("Not able to find TOML file on path $tomlFile: ${e.message}") + throw FileNotFoundException("Not able to find TOML file on path $filePath: ${e.message}") } } diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt index 3443580d..b36b020e 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt @@ -1,14 +1,13 @@ - package com.akuleshov7.ktoml.file -import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.TomlConfig - -import kotlin.native.concurrent.ThreadLocal +import com.akuleshov7.ktoml.source.TomlSourceReader import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.serializer +import kotlin.native.concurrent.ThreadLocal /** * TomlFile class can be used for reading files in TOML format @@ -17,9 +16,10 @@ import kotlinx.serialization.modules.SerializersModule */ @OptIn(ExperimentalSerializationApi::class) public open class TomlFileReader( - private val config: TomlConfig = TomlConfig(), + config: TomlConfig = TomlConfig(), override val serializersModule: SerializersModule = EmptySerializersModule -) : Toml(config, serializersModule) { +) : TomlSourceReader(config, serializersModule) { + /** * Simple deserializer of a file that contains toml. Reading file with okio native library * @@ -31,8 +31,19 @@ public open class TomlFileReader( deserializer: DeserializationStrategy, tomlFilePath: String, ): T { - val parsedToml = readAndParseFile(tomlFilePath) - return decodeFromString(deserializer, parsedToml, config) + return decodeFromSource(deserializer, getFileSource(tomlFilePath)) + } + + /** + * Simple deserializer of a file that contains toml. Reading file with okio native library + * + * @param tomlFilePath path to the file where toml is stored + * @return deserialized object of type T + */ + public inline fun decodeFromFile( + tomlFilePath: String, + ): T { + return decodeFromFile(serializersModule.serializer(), tomlFilePath) } /** @@ -53,8 +64,26 @@ public open class TomlFileReader( tomlFilePath: String, tomlTableName: String, ): T { - val parsedToml = readAndParseFile(tomlFilePath) - return partiallyDecodeFromString(deserializer, parsedToml, tomlTableName, config) + return partiallyDecodeFromSource(deserializer, getFileSource(tomlFilePath), tomlTableName) + } + + /** + * Partial deserializer of a file that contains toml. Reading file with okio native library. + * Will deserialize only the part presented under the tomlTableName table. + * If such table is missing in he input - will throw an exception. + * + * (!) Useful when you would like to deserialize only ONE table + * and you do not want to reproduce whole object structure in the code + * + * @param tomlFilePath path to the file where toml is stored + * @param tomlTableName fully qualified name of the toml table (it should be the full name - a.b.c.d) + * @return deserialized object of type T + */ + public inline fun partiallyDecodeFromFile( + tomlFilePath: String, + tomlTableName: String, + ): T { + return partiallyDecodeFromFile(serializersModule.serializer(), tomlFilePath, tomlTableName) } /** diff --git a/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt index 1715a789..0b041ec6 100644 --- a/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt +++ b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt @@ -1,7 +1,8 @@ package com.akuleshov7.ktoml.file -import com.akuleshov7.ktoml.* +import com.akuleshov7.ktoml.TomlConfig import com.akuleshov7.ktoml.parsers.TomlParser +import com.akuleshov7.ktoml.source.useLines import com.akuleshov7.ktoml.tree.TomlTablePrimitive import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable @@ -50,7 +51,10 @@ class TomlFileParserTest { ) assertEquals( expected, - TomlFileReader().decodeFromFile(serializer(), "src/commonTest/resources/simple_example.toml") + TomlFileReader().decodeFromFile( + serializer(), + "src/commonTest/resources/simple_example.toml" + ) ) } @@ -81,9 +85,12 @@ class TomlFileParserTest { val test = MyTableTest(A(Ab(InnerTest("Undefined")), InnerTest("Undefined")), D(InnerTest("Undefined"))) assertEquals(test, TomlFileReader.decodeFromFile(serializer(), file)) // ==== checking how table discovery works - val lines = readAndParseFile(file) - val parsedResult = TomlParser(TomlConfig()).parseStringsToTomlTree(lines, TomlConfig()) - assertEquals(listOf("a", "a.b.c", "a.d", "d", "d.a"), parsedResult.getRealTomlTables().map { it.fullTableName }) + val parsedResult = getFileSource(file).useLines { lines -> + TomlParser(TomlConfig()).parseStringsToTomlTree(lines, TomlConfig()) + } + assertEquals( + listOf("a", "a.b.c", "a.d", "d", "d.a"), + parsedResult.getRealTomlTables().map { it.fullTableName }) } @Serializable @@ -191,15 +198,16 @@ class TomlFileParserTest { @Test fun readTopLevelTables() { val file = "src/commonTest/resources/simple_example.toml" - val lines = readAndParseFile(file) assertEquals( listOf("owner", "database"), - TomlParser(TomlConfig()) - .parseStringsToTomlTree(lines, TomlConfig()) - .children - .filterIsInstance() - .filter { !it.isSynthetic } - .map { it.fullTableName } + getFileSource(file).useLines { lines -> + TomlParser(TomlConfig()) + .parseStringsToTomlTree(lines, TomlConfig()) + .children + .filterIsInstance() + .filter { !it.isSynthetic } + .map { it.fullTableName } + } ) } } diff --git a/ktoml-source/build.gradle.kts b/ktoml-source/build.gradle.kts new file mode 100644 index 00000000..2a93d6e3 --- /dev/null +++ b/ktoml-source/build.gradle.kts @@ -0,0 +1,90 @@ +import com.akuleshov7.buildutils.configurePublishing +import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest + +plugins { + kotlin("multiplatform") + kotlin("plugin.serialization") +} + +kotlin { + explicitApi() + + jvm { + compilations.all { + kotlinOptions { + jvmTarget = "11" + } + } + } + + mingwX64() + linuxX64() + macosX64() + + sourceSets { + all { + languageSettings.optIn("kotlin.RequiresOptIn") + } + + val linuxX64Main by getting { + dependencies { + implementation("com.squareup.okio:okio:${Versions.OKIO}") + implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") + } + } + + val mingwX64Main by getting { + dependencies { + implementation("com.squareup.okio:okio:${Versions.OKIO}") + implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") + } + } + + val macosX64Main by getting { + dependencies { + implementation("com.squareup.okio:okio:${Versions.OKIO}") + implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") + } + } + + val jvmMain by getting { + dependencies { + implementation("com.squareup.okio:okio:${Versions.OKIO}") + implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") + } + } + + val commonMain by getting { + dependencies { + implementation("com.squareup.okio:okio:${Versions.OKIO}") + implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") + implementation(project(":ktoml-core")) + } + } + + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + + val jvmTest by getting { + dependsOn(commonTest) + dependencies { + implementation(kotlin("test-junit5")) + implementation("org.junit.jupiter:junit-jupiter-engine:5.0.0") + } + } + + all { + languageSettings.enableLanguageFeature("InlineClasses") + } + } +} + +configurePublishing() + +tasks.withType { + useJUnitPlatform() +} diff --git a/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/SourceUtils.kt b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/SourceUtils.kt new file mode 100644 index 00000000..74438ad9 --- /dev/null +++ b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/SourceUtils.kt @@ -0,0 +1,14 @@ +package com.akuleshov7.ktoml.source + +import okio.Source +import okio.buffer +import okio.use + +/** + * Read from source one line at a time and passes the lines to the [decoder] function. + */ +public inline fun Source.useLines(decoder: (Sequence) -> T): T { + return buffer().use { source -> + decoder(generateSequence { source.readUtf8Line() }) + } +} \ No newline at end of file diff --git a/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt new file mode 100644 index 00000000..3ec64563 --- /dev/null +++ b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt @@ -0,0 +1,96 @@ +package com.akuleshov7.ktoml.source + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.TomlConfig +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.serializer +import okio.Source +import kotlin.native.concurrent.ThreadLocal + +/** + * This class can be used for reading [Source] in TOML format + * @property serializersModule + */ +@OptIn(ExperimentalSerializationApi::class) +public open class TomlSourceReader( + private val config: TomlConfig = TomlConfig(), + override val serializersModule: SerializersModule = EmptySerializersModule +) : Toml(config, serializersModule) { + + /** + * Simple deserializer of a source that contains toml. + * + * @param deserializer deserialization strategy + * @param source source where toml is stored + * @return deserialized object of type T + */ + public fun decodeFromSource( + deserializer: DeserializationStrategy, + source: Source, + ): T { + return source.useLines { lines -> decodeFromString(deserializer, lines, config) } + } + + /** + * Simple deserializer of a source that contains toml. + * + * @param source source where toml is stored + * @return deserialized object of type T + */ + public inline fun decodeFromSource(source: Source): T { + return decodeFromSource(serializersModule.serializer(), source) + } + + /** + * Partial deserializer of a file that contains toml. Reading file with okio native library. + * Will deserialize only the part presented under the tomlTableName table. + * If such table is missing in he input - will throw an exception. + * + * (!) Useful when you would like to deserialize only ONE table + * and you do not want to reproduce whole object structure in the code + * + * @param deserializer deserialization strategy + * @param source source where toml is stored + * @param tomlTableName fully qualified name of the toml table (it should be the full name - a.b.c.d) + * @return deserialized object of type T + */ + public fun partiallyDecodeFromSource( + deserializer: DeserializationStrategy, + source: Source, + tomlTableName: String, + ): T { + return source.useLines { lines -> + partiallyDecodeFromLines(deserializer, lines, tomlTableName, config) + } + } + + /** + * Partial deserializer of a file that contains toml. Reading file with okio native library. + * Will deserialize only the part presented under the tomlTableName table. + * If such table is missing in he input - will throw an exception. + * + * (!) Useful when you would like to deserialize only ONE table + * and you do not want to reproduce whole object structure in the code + * + * @param source source where toml is stored + * @param tomlTableName fully qualified name of the toml table (it should be the full name - a.b.c.d) + * @return deserialized object of type T + */ + public inline fun partiallyDecodeFromSource( + source: Source, + tomlTableName: String, + ): T { + return partiallyDecodeFromSource(serializersModule.serializer(), source, tomlTableName) + } + + /** + * The default instance of [TomlSourceReader] with the default configuration. + * See [TomlConfig] for the list of the default options + * ThreadLocal annotation is used here for caching. + */ + @ThreadLocal + public companion object Default : TomlSourceReader(TomlConfig()) +} diff --git a/ktoml-source/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlSourceParserTest.kt b/ktoml-source/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlSourceParserTest.kt new file mode 100644 index 00000000..58ad5f42 --- /dev/null +++ b/ktoml-source/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlSourceParserTest.kt @@ -0,0 +1,75 @@ +package com.akuleshov7.ktoml.file + +import com.akuleshov7.ktoml.source.TomlSourceReader +import kotlinx.serialization.Serializable +import kotlinx.serialization.serializer +import okio.Buffer +import kotlin.test.Test +import kotlin.test.assertEquals + +class TomlSourceParserTest { + @Serializable + data class TestClass( + val title: String, + val owner: Owner, + val database: Database + ) + + @Serializable + data class Owner( + val name: String, + val dob: String, + val mytest: MyTest + ) + + @Serializable + data class Database( + val server: String, + ) + + @Serializable + data class MyTest( + val myserver: String, + val myotherserver: String + ) + + @Test + fun readParseAndDecodeSource() { + val expected = TestClass( + "TOML \"Example\"", + Owner( + "Tom Preston-Werner", + "1979-05-27T07:32:00-08:00", + MyTest("test", "this is my \\ special \" [ value \" / ") + ), + Database( + "192.168.1.1" + ) + ) + val buffer = Buffer() + buffer.writeUtf8(SIMPLE_EXAMPLE) + assertEquals( + expected, + TomlSourceReader.decodeFromSource(serializer(), buffer) + ) + } + + companion object { + private val SIMPLE_EXAMPLE = """ + # This is a TOML document. + + title = "TOML \"Example\"" + + [owner] + name = "Tom Preston-Werner" + dob = "1979-05-27T07:32:00-08:00" # First class dates + + [database] + server = "192.168.1.1" + + [owner.mytest] + myserver = "test" + myotherserver = 'this is my \ special " [ value " / ' + """.trimIndent() + } +} diff --git a/ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt b/ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt new file mode 100644 index 00000000..5c221ba3 --- /dev/null +++ b/ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt @@ -0,0 +1,69 @@ +package com.akuleshov7.ktoml.source + +import com.akuleshov7.ktoml.Toml +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.serializer +import okio.source +import java.io.InputStream + +/** + * Deserializes TOML from [stream] using UTF-8 encoding to a value of type [T] using [deserializer]. + */ +public fun Toml.decodeFromStream( + deserializer: DeserializationStrategy, + stream: InputStream +): T { + return stream.source().useLines { lines -> + decodeFromString(deserializer, lines) + } +} + +/** + * Deserializes the contents of given [stream] to the value of type [T] using UTF-8 encoding and + * deserializer retrieved from the reified type parameter. + */ +public inline fun Toml.decodeFromStream(stream: InputStream): T { + return decodeFromStream(serializersModule.serializer(), stream) +} + +/** + * Partial deserializer of a stream that contains toml. + * Will deserialize only the part presented under the tomlTableName table. + * If such table is missing in he input - will throw an exception. + * + * (!) Useful when you would like to deserialize only ONE table + * and you do not want to reproduce whole object structure in the code + * + * @param deserializer deserialization strategy + * @param stream stream where toml is stored + * @param tomlTableName fully qualified name of the toml table (it should be the full name - a.b.c.d) + * @return deserialized object of type T + */ +public fun Toml.partiallyDecodeFromStream( + deserializer: DeserializationStrategy, + stream: InputStream, + tomlTableName: String +): T { + return stream.source().useLines { lines -> + partiallyDecodeFromLines(deserializer, lines, tomlTableName) + } +} + +/** + * Partial deserializer of a stream that contains toml. + * Will deserialize only the part presented under the tomlTableName table. + * If such table is missing in he input - will throw an exception. + * + * (!) Useful when you would like to deserialize only ONE table + * and you do not want to reproduce whole object structure in the code + * + * @param stream stream where toml is stored + * @param tomlTableName fully qualified name of the toml table (it should be the full name - a.b.c.d) + * @return deserialized object of type T + */ +public inline fun Toml.partiallyDecodeFromStream( + stream: InputStream, + tomlTableName: String +): T { + return partiallyDecodeFromStream(serializersModule.serializer(), stream, tomlTableName) +} \ No newline at end of file diff --git a/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt b/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt new file mode 100644 index 00000000..32e52590 --- /dev/null +++ b/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt @@ -0,0 +1,219 @@ +package com.akuleshov7.ktoml.source + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.parsers.TomlParser +import com.akuleshov7.ktoml.tree.TomlTablePrimitive +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import okio.source +import org.junit.jupiter.api.Test +import java.io.InputStream +import kotlin.test.assertEquals + +class StreamTests { + @Serializable + data class TestClass( + val title: String, + val owner: Owner, + val database: Database + ) + + @Serializable + data class Owner( + val name: String, + val dob: String, + val mytest: MyTest + ) + + @Serializable + data class Database( + val server: String, + ) + + @Serializable + data class MyTest( + val myserver: String, + val myotherserver: String + ) + + @Test + fun readParseAndDecodeStream() { + val expected = TestClass( + "TOML \"Example\"", + Owner( + "Tom Preston-Werner", + "1979-05-27T07:32:00-08:00", + MyTest("test", "this is my \\ special \" [ value \" / ") + ), + Database( + "192.168.1.1" + ) + ) + assertEquals( + expected, + Toml().decodeFromStream(getTestDataStream("simple_example.toml")) + ) + } + + // ================ + @Serializable + data class MyTableTest( + val a: A, + val d: D + ) + + @Serializable + data class A(val b: Ab, val d: InnerTest) + + @Serializable + data class Ab(val c: InnerTest) + + @Serializable + data class D(val a: InnerTest) + + @Serializable + data class InnerTest(val str: String = "Undefined") + + @Test + @ExperimentalSerializationApi + fun testTableDiscovery() { + // ==== reading from stream + val test = MyTableTest( + A(Ab(InnerTest("Undefined")), InnerTest("Undefined")), + D(InnerTest("Undefined")) + ) + assertEquals(test, Toml().decodeFromStream(getTestDataStream("complex_toml_tables.toml"))) + // ==== checking how table discovery works + val parsedResult = + getTestDataStream("complex_toml_tables.toml").source().useLines { lines -> + TomlParser(TomlConfig()).parseStringsToTomlTree(lines, TomlConfig()) + } + assertEquals( + listOf("a", "a.b.c", "a.d", "d", "d.a"), + parsedResult.getRealTomlTables().map { it.fullTableName }) + } + + @Serializable + data class RegressionTest(val a: Long?, val b: Long, val c: Long, val d: Long?) + + @ExperimentalSerializationApi + @Test + fun regressionCast2Test() { + val parsedResult = + Toml().decodeFromStream(getTestDataStream("class_cast_regression2.toml")) + assertEquals(RegressionTest(null, 1, 2, null), parsedResult) + } + + @ExperimentalSerializationApi + @Test + fun regressionPartialTest() { + val parsedResult = + Toml().decodeFromStream(getTestDataStream("class_cast_regression2.toml")) + assertEquals(RegressionTest(null, 1, 2, null), parsedResult) + } + + + @Serializable + data class TestRegression( + val list1: List, + val general: GeneralConfig, + val list2: List, + val warn: WarnConfig, + val list3: List + ) + + @Serializable + data class GeneralConfig( + val execCmd: String? = null, + val tags: List? = null, + val description: String? = null, + val suiteName: String? = null, + val excludedTests: List? = null, + val includedTests: List? = null, + val ignoreSaveComments: Boolean? = null + ) + + @Serializable + data class WarnConfig( + val list: List + ) + + @ExperimentalSerializationApi + @Test + fun regressionInvalidIndex() { + assertEquals( + GeneralConfig( + execCmd = "echo hello world", + tags = listOf("Tag", "Other tag"), + description = "My description", + suiteName = "// DocsCheck", + excludedTests = null, + includedTests = null, + ignoreSaveComments = null + ), + Toml().partiallyDecodeFromStream( + getTestDataStream("partial_parser_regression.toml"), + "general" + ) + ) + assertEquals( + TestRegression( + list1 = listOf(1.0, 2.0), + general = GeneralConfig( + execCmd = "echo hello world", + tags = listOf("Tag", "Other tag"), + description = "My description", + suiteName = "// DocsCheck", + excludedTests = null, + includedTests = null, + ignoreSaveComments = null + ), + list2 = listOf(1, 3, 5), + warn = WarnConfig(list = listOf("12a", "12f")), + list3 = listOf("mystr", "2", "3") + ), + Toml().decodeFromStream(getTestDataStream("partial_parser_regression.toml")) + ) + } + + @Serializable + data class Table1(val a: Long, val b: Long) + + @Serializable + data class Table2(val c: Long, val e: Long, val d: Long) + + @Serializable + data class TwoTomlTables(val table1: Table1, val table2: Table2) + + @Test + fun testPartialFileDecoding() { + val test = TwoTomlTables(Table1(1, 2), Table2(1, 2, 3)) + assertEquals( + test.table1, + Toml().partiallyDecodeFromStream( + getTestDataStream("partial_decoder.toml"), + "table1" + ) + ) + } + + @Test + fun readTopLevelTables() { + assertEquals( + listOf("owner", "database"), + getTestDataStream("simple_example.toml").source().useLines { lines -> + TomlParser(TomlConfig()) + .parseStringsToTomlTree(lines, TomlConfig()) + .children + .filterIsInstance() + .filter { !it.isSynthetic } + .map { it.fullTableName } + } + ) + } + + private fun getTestDataStream(name: String): InputStream { + return requireNotNull(StreamTests::class.java.getResourceAsStream(name)) + } +} \ No newline at end of file diff --git a/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/class_cast_regression1.toml b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/class_cast_regression1.toml new file mode 100644 index 00000000..c3c67301 --- /dev/null +++ b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/class_cast_regression1.toml @@ -0,0 +1,4 @@ +a = 1 +b = null +c = 2 +d = null diff --git a/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/class_cast_regression2.toml b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/class_cast_regression2.toml new file mode 100644 index 00000000..f32f8c69 --- /dev/null +++ b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/class_cast_regression2.toml @@ -0,0 +1,4 @@ +a = null +b = 1 +c = 2 +d = null diff --git a/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/complex_toml_tables.toml b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/complex_toml_tables.toml new file mode 100644 index 00000000..98028862 --- /dev/null +++ b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/complex_toml_tables.toml @@ -0,0 +1,8 @@ +[a] + [a.b.c] + [a.d] + +[d] + [d.a] + + diff --git a/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/partial_decoder.toml b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/partial_decoder.toml new file mode 100644 index 00000000..6f6b0feb --- /dev/null +++ b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/partial_decoder.toml @@ -0,0 +1,7 @@ +[table1] + a = 1 + b = 2 + [table2] + c = 1 + e = 2 + d = 3 \ No newline at end of file diff --git a/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/partial_parser_regression.toml b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/partial_parser_regression.toml new file mode 100644 index 00000000..04c4835d --- /dev/null +++ b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/partial_parser_regression.toml @@ -0,0 +1,12 @@ +list1 = [1.0, 2.0] +list2 = [1, 3, 5] +list3 = ["mystr", "2", "3"] + +[general] +execCmd = "echo hello world" +tags = ["Tag", "Other tag"] +description = "My description" +suiteName = "// DocsCheck" + +[warn] +list = ["12a", "12f"] diff --git a/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/simple_example.toml b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/simple_example.toml new file mode 100644 index 00000000..cd84341a --- /dev/null +++ b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/simple_example.toml @@ -0,0 +1,14 @@ +# This is a TOML document. + +title = "TOML \"Example\"" + +[owner] +name = "Tom Preston-Werner" +dob = "1979-05-27T07:32:00-08:00" # First class dates + +[database] +server = "192.168.1.1" + + [owner.mytest] + myserver = "test" + myotherserver = 'this is my \ special " [ value " / ' \ No newline at end of file diff --git a/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/toml_tables_order.toml b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/toml_tables_order.toml new file mode 100644 index 00000000..34c26d62 --- /dev/null +++ b/ktoml-source/src/jvmTest/resources/com/akuleshov7/ktoml/source/toml_tables_order.toml @@ -0,0 +1,8 @@ +[fruit.apple] +a = 5 +[animal] +c = 7 + +[fruit.orange] +[fruit] +b = 5 diff --git a/settings.gradle.kts b/settings.gradle.kts index d131c003..471d9adc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,4 @@ rootProject.name = "ktoml" include("ktoml-core") include("ktoml-file") +include("ktoml-source") From 3b6b23ea2f0425f17704ab9202eae76966d6f626 Mon Sep 17 00:00:00 2001 From: Valentin Rocher Date: Thu, 24 Feb 2022 11:25:22 +0100 Subject: [PATCH 2/5] replace list with dequeue in sequence trimming --- .../com/akuleshov7/ktoml/parsers/TomlParser.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 de200b11..77efa8b3 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 @@ -144,12 +144,13 @@ public value class TomlParser(private val config: TomlConfig) { private var nextState: Int = -1 private var nextItem: String? = null private var nextRealItem: String? = null - private val emptyLinesBuffer: MutableList = mutableListOf() + private val emptyLinesBuffer = ArrayDeque() private fun calcNext() { - if (emptyLinesBuffer.isNotEmpty()) { + var nextEmptyLine = emptyLinesBuffer.removeFirstOrNull() + if (nextEmptyLine != null) { nextState = 1 - nextItem = emptyLinesBuffer.removeFirst() + nextItem = nextEmptyLine return } if (nextRealItem != null) { @@ -164,13 +165,14 @@ public value class TomlParser(private val config: TomlConfig) { emptyLinesBuffer.add(line) } else { nextRealItem = line - if (emptyLinesBuffer.isEmpty()) { + nextEmptyLine = emptyLinesBuffer.removeFirstOrNull() + if (nextEmptyLine == null) { nextState = 2 nextItem = nextRealItem nextRealItem = null } else { nextState = 1 - nextItem = emptyLinesBuffer.removeFirst() + nextItem = nextEmptyLine } return } From b2238c85585aabdfe61f94feb705323f1a9b7e86 Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Wed, 4 May 2022 16:12:54 +0300 Subject: [PATCH 3/5] Fixing diktat issues and adding small corrections --- README.md | 3 + .../buildutils/DiktatConfiguration.kt | 4 +- .../kotlin/com/akuleshov7/ktoml/Toml.kt | 4 +- .../akuleshov7/ktoml/parsers/TomlParser.kt | 125 ++++---- .../com/akuleshov7/ktoml/file/FileUtils.kt | 4 +- .../akuleshov7/ktoml/file/TomlFileReader.kt | 20 +- .../ktoml/file/TomlFileParserTest.kt | 2 +- .../akuleshov7/ktoml/source/SourceUtils.kt | 15 +- .../ktoml/source/TomlSourceReader.kt | 25 +- .../com/akuleshov7/ktoml/source/JvmStreams.kt | 37 +-- .../akuleshov7/ktoml/source/StreamTests.kt | 266 ++++++++++++------ 11 files changed, 288 insertions(+), 217 deletions(-) diff --git a/README.md b/README.md index 0f4c134b..ebb0174b 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,9 @@ You can check types that are supported in TOML [here](https://toml.io/en/v1.0.0# We will support all Kotlin primitive types in the future with the non-strict configuration of ktoml, but now only String, Long, Double and Boolean are supported from the list of Kotlin primitives. +After some brainstorming we finally decided to stream the decoded data. So we use `Sequences` instead of `Collections` in ktoml. +We think that it should give users a better performance and user-friendly API. + **General** \ We are still developing and testing this library, so it has several limitations: \ :white_check_mark: deserialization (with some parsing limitations) \ diff --git a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt b/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt index 02e9c1b1..99ea55a3 100644 --- a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt +++ b/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt @@ -30,10 +30,10 @@ fun Project.configureDiktat() { // using `Project#path` here, because it must be unique in gradle's project hierarchy if (path == rootProject.path) { include("$rootDir/buildSrc/src/**/*.kt", "$rootDir/*.kts", "$rootDir/buildSrc/**/*.kts") - exclude("src/test/**/*.kt", "src/commonTest/**/*.kt") // path matching this pattern will not be checked by diktat + exclude("src/test/**/*.kt", "src/commonTest/**/*.kt", "src/jvmTest/**/*.kt") // path matching this pattern will not be checked by diktat } else { include("src/**/*.kt", "**/*.kts") - exclude("src/**test/**/*.kt", "src/commonTest/**/*.kt") // path matching this pattern will not be checked by diktat + exclude("src/**test/**/*.kt", "src/commonTest/**/*.kt", "src/jvmTest/**/*.kt") // path matching this pattern will not be checked by diktat } } } 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 32827f8a..c2eedb29 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt @@ -65,9 +65,7 @@ public open class Toml( deserializer: DeserializationStrategy, toml: List, config: TomlConfig - ): T { - return decodeFromString(deserializer, toml.asSequence(), config) - } + ): T = decodeFromString(deserializer, toml.asSequence(), config) /** * simple deserializer of a sequence of strings in a toml format 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 77efa8b3..656f47fa 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 @@ -10,7 +10,6 @@ import kotlin.jvm.JvmInline @JvmInline @Suppress("WRONG_MULTIPLE_MODIFIERS_ORDER") public value class TomlParser(private val config: TomlConfig) { - /** * Method for parsing of TOML string (this string should be split with newlines \n or \r\n) * @@ -41,9 +40,7 @@ public value class TomlParser(private val config: TomlConfig) { * @return the root node of the resulted toml tree * @throws InternalAstException - if toml node does not inherit TomlNode class */ - public fun parseStringsToTomlTree(tomlLines: List, config: TomlConfig): TomlFile { - return parseStringsToTomlTree(tomlLines.asSequence(), config) - } + public fun parseStringsToTomlTree(tomlLines: List, config: TomlConfig): TomlFile = parseStringsToTomlTree(tomlLines.asSequence(), config) /** * Parsing the list of strings to the TOML intermediate representation (TOML- abstract syntax tree). @@ -53,7 +50,7 @@ public value class TomlParser(private val config: TomlConfig) { * @return the root node of the resulted toml tree * @throws InternalAstException - if toml node does not inherit TomlNode class */ - @Suppress("TOO_LONG_FUNCTION") + @Suppress("TOO_LONG_FUNCTION", "NESTED_BLOCK") public fun parseStringsToTomlTree(tomlLines: Sequence, config: TomlConfig): TomlFile { var currentParentalNode: TomlNode = TomlFile(config) // link to the head of the tree @@ -65,6 +62,7 @@ public value class TomlParser(private val config: TomlConfig) { var index = 0 val linesIterator = trimmedTomlLines.iterator() + // all lines will be streamed sequentially while (linesIterator.hasNext()) { val line = linesIterator.next() val lineNo = index + 1 @@ -132,74 +130,71 @@ public value class TomlParser(private val config: TomlConfig) { } } - // This code is eavily inspired by the TransformingSequence code in kotlin-lib - private fun Sequence.trimEmptyLines(): Sequence { - return object : Sequence { - - override fun iterator(): Iterator { - return object : Iterator { - private val linesIterator = this@trimEmptyLines.iterator() - - // -1 for unknown, 0 for done, 1 for empty lines, 2 for continue - private var nextState: Int = -1 - private var nextItem: String? = null - private var nextRealItem: String? = null - private val emptyLinesBuffer = ArrayDeque() - - private fun calcNext() { - var nextEmptyLine = emptyLinesBuffer.removeFirstOrNull() - if (nextEmptyLine != null) { + @Suppress("TOO_LONG_FUNCTION") + // This code is heavily inspired by the TransformingSequence code in kotlin-lib, simple trimming of empty lines + private fun Sequence.trimEmptyLines(): Sequence = object : Sequence { + override fun iterator(): Iterator = object : Iterator { + private val linesIterator = this@trimEmptyLines.iterator() + + // -1 for unknown, 0 for done, 1 for empty lines, 2 for continue + private var nextState: Int = -1 + private var nextItem: String? = null + private var nextRealItem: String? = null + private val emptyLinesBuffer: ArrayDeque = ArrayDeque() + + private fun calcNext() { + var nextEmptyLine = emptyLinesBuffer.removeFirstOrNull() + nextEmptyLine?.let { + nextState = 1 + nextItem = nextEmptyLine + return + } + nextRealItem?.let { + nextState = 2 + nextItem = nextRealItem + nextRealItem = null + return + } + while (linesIterator.hasNext()) { + val line = linesIterator.next() + if (line.isEmptyLine()) { + emptyLinesBuffer.add(line) + } else { + nextRealItem = line + nextEmptyLine = emptyLinesBuffer.removeFirstOrNull() + nextEmptyLine?.let { nextState = 1 nextItem = nextEmptyLine - return - } - if (nextRealItem != null) { - nextState = 2 - nextItem = nextRealItem - nextRealItem = null - return } - while (linesIterator.hasNext()) { - val line = linesIterator.next() - if (line.isEmptyLine()) { - emptyLinesBuffer.add(line) - } else { - nextRealItem = line - nextEmptyLine = emptyLinesBuffer.removeFirstOrNull() - if (nextEmptyLine == null) { - nextState = 2 - nextItem = nextRealItem - nextRealItem = null - } else { - nextState = 1 - nextItem = nextEmptyLine - } - return + ?: run { + nextState = 2 + nextItem = nextRealItem + nextRealItem = null } - } - nextState = 0 + return } + } + nextState = 0 + } - override fun hasNext(): Boolean { - if (nextState == -1) { - calcNext() - } - return nextState == 1 || nextState == 2 - } + override fun hasNext(): Boolean { + if (nextState == -1) { + calcNext() + } + return nextState == 1 || nextState == 2 + } - override fun next(): String { - if (nextState == -1) { - calcNext() - } - if (nextState == 0) { - throw NoSuchElementException() - } - val result = nextItem - nextItem = null - nextState = -1 - return result as String - } + override fun next(): String { + if (nextState == -1) { + calcNext() + } + if (nextState == 0) { + throw NoSuchElementException() } + val result = nextItem + nextItem = null + nextState = -1 + return result as String } } } 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 51723b6a..7e529bf2 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 @@ -7,14 +7,14 @@ package com.akuleshov7.ktoml.file import okio.BufferedSink import okio.FileNotFoundException import okio.FileSystem -import okio.Source import okio.Path.Companion.toPath +import okio.Source import okio.buffer /** * Simple file reading with okio (returning a list with strings) * - * @param tomlFile string with a path to a file + * @param filePath string with a path to a file * @return list with strings * @throws FileNotFoundException if the toml file is missing */ diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt index b36b020e..58a5dd40 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt @@ -2,12 +2,13 @@ package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.TomlConfig import com.akuleshov7.ktoml.source.TomlSourceReader + +import kotlin.native.concurrent.ThreadLocal import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer -import kotlin.native.concurrent.ThreadLocal /** * TomlFile class can be used for reading files in TOML format @@ -19,7 +20,6 @@ public open class TomlFileReader( config: TomlConfig = TomlConfig(), override val serializersModule: SerializersModule = EmptySerializersModule ) : TomlSourceReader(config, serializersModule) { - /** * Simple deserializer of a file that contains toml. Reading file with okio native library * @@ -30,9 +30,7 @@ public open class TomlFileReader( public fun decodeFromFile( deserializer: DeserializationStrategy, tomlFilePath: String, - ): T { - return decodeFromSource(deserializer, getFileSource(tomlFilePath)) - } + ): T = decodeFromSource(deserializer, getFileSource(tomlFilePath)) /** * Simple deserializer of a file that contains toml. Reading file with okio native library @@ -42,9 +40,7 @@ public open class TomlFileReader( */ public inline fun decodeFromFile( tomlFilePath: String, - ): T { - return decodeFromFile(serializersModule.serializer(), tomlFilePath) - } + ): T = decodeFromFile(serializersModule.serializer(), tomlFilePath) /** * Partial deserializer of a file that contains toml. Reading file with okio native library. @@ -63,9 +59,7 @@ public open class TomlFileReader( deserializer: DeserializationStrategy, tomlFilePath: String, tomlTableName: String, - ): T { - return partiallyDecodeFromSource(deserializer, getFileSource(tomlFilePath), tomlTableName) - } + ): T = partiallyDecodeFromSource(deserializer, getFileSource(tomlFilePath), tomlTableName) /** * Partial deserializer of a file that contains toml. Reading file with okio native library. @@ -82,9 +76,7 @@ public open class TomlFileReader( public inline fun partiallyDecodeFromFile( tomlFilePath: String, tomlTableName: String, - ): T { - return partiallyDecodeFromFile(serializersModule.serializer(), tomlFilePath, tomlTableName) - } + ): T = partiallyDecodeFromFile(serializersModule.serializer(), tomlFilePath, tomlTableName) /** * The default instance of [TomlFileReader] with the default configuration. diff --git a/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt index 52353973..59d2b911 100644 --- a/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt +++ b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt @@ -216,7 +216,7 @@ class TomlFileParserTest { fun invalidFile() { val file = "src/commonTest/resources/simple_example.wrongext" assertFailsWith { - readAndParseFile(file) + getFileSource(file) } } } diff --git a/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/SourceUtils.kt b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/SourceUtils.kt index 74438ad9..ac4ee53c 100644 --- a/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/SourceUtils.kt +++ b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/SourceUtils.kt @@ -1,3 +1,7 @@ +/** + * Utility methods for the ktoml-source module + */ + package com.akuleshov7.ktoml.source import okio.Source @@ -6,9 +10,10 @@ import okio.use /** * Read from source one line at a time and passes the lines to the [decoder] function. + * + * @param decoder + * @return decoded lines */ -public inline fun Source.useLines(decoder: (Sequence) -> T): T { - return buffer().use { source -> - decoder(generateSequence { source.readUtf8Line() }) - } -} \ No newline at end of file +public inline fun Source.useLines(decoder: (Sequence) -> T): T = buffer().use { source -> + decoder(generateSequence { source.readUtf8Line() }) +} diff --git a/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt index 3ec64563..e1917d7b 100644 --- a/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt +++ b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt @@ -2,13 +2,15 @@ package com.akuleshov7.ktoml.source import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.TomlConfig + +import okio.Source + +import kotlin.native.concurrent.ThreadLocal import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer -import okio.Source -import kotlin.native.concurrent.ThreadLocal /** * This class can be used for reading [Source] in TOML format @@ -19,7 +21,6 @@ public open class TomlSourceReader( private val config: TomlConfig = TomlConfig(), override val serializersModule: SerializersModule = EmptySerializersModule ) : Toml(config, serializersModule) { - /** * Simple deserializer of a source that contains toml. * @@ -30,9 +31,7 @@ public open class TomlSourceReader( public fun decodeFromSource( deserializer: DeserializationStrategy, source: Source, - ): T { - return source.useLines { lines -> decodeFromString(deserializer, lines, config) } - } + ): T = source.useLines { lines -> decodeFromString(deserializer, lines, config) } /** * Simple deserializer of a source that contains toml. @@ -40,9 +39,7 @@ public open class TomlSourceReader( * @param source source where toml is stored * @return deserialized object of type T */ - public inline fun decodeFromSource(source: Source): T { - return decodeFromSource(serializersModule.serializer(), source) - } + public inline fun decodeFromSource(source: Source): T = decodeFromSource(serializersModule.serializer(), source) /** * Partial deserializer of a file that contains toml. Reading file with okio native library. @@ -61,10 +58,8 @@ public open class TomlSourceReader( deserializer: DeserializationStrategy, source: Source, tomlTableName: String, - ): T { - return source.useLines { lines -> - partiallyDecodeFromLines(deserializer, lines, tomlTableName, config) - } + ): T = source.useLines { lines -> + partiallyDecodeFromLines(deserializer, lines, tomlTableName, config) } /** @@ -82,9 +77,7 @@ public open class TomlSourceReader( public inline fun partiallyDecodeFromSource( source: Source, tomlTableName: String, - ): T { - return partiallyDecodeFromSource(serializersModule.serializer(), source, tomlTableName) - } + ): T = partiallyDecodeFromSource(serializersModule.serializer(), source, tomlTableName) /** * The default instance of [TomlSourceReader] with the default configuration. diff --git a/ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt b/ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt index 5c221ba3..53900d92 100644 --- a/ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt +++ b/ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt @@ -1,30 +1,39 @@ +/** + * Utility methods for the decoding of JVM streams + */ + package com.akuleshov7.ktoml.source import com.akuleshov7.ktoml.Toml -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.serializer + import okio.source + import java.io.InputStream +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.serializer + /** * Deserializes TOML from [stream] using UTF-8 encoding to a value of type [T] using [deserializer]. + * + * @param deserializer + * @param stream + * @return decoded lines */ public fun Toml.decodeFromStream( deserializer: DeserializationStrategy, stream: InputStream -): T { - return stream.source().useLines { lines -> - decodeFromString(deserializer, lines) - } +): T = stream.source().useLines { lines -> + decodeFromString(deserializer, lines) } /** * Deserializes the contents of given [stream] to the value of type [T] using UTF-8 encoding and * deserializer retrieved from the reified type parameter. + * + * @param stream */ -public inline fun Toml.decodeFromStream(stream: InputStream): T { - return decodeFromStream(serializersModule.serializer(), stream) -} +public inline fun Toml.decodeFromStream(stream: InputStream): T = decodeFromStream(serializersModule.serializer(), stream) /** * Partial deserializer of a stream that contains toml. @@ -43,10 +52,8 @@ public fun Toml.partiallyDecodeFromStream( deserializer: DeserializationStrategy, stream: InputStream, tomlTableName: String -): T { - return stream.source().useLines { lines -> - partiallyDecodeFromLines(deserializer, lines, tomlTableName) - } +): T = stream.source().useLines { lines -> + partiallyDecodeFromLines(deserializer, lines, tomlTableName) } /** @@ -64,6 +71,4 @@ public fun Toml.partiallyDecodeFromStream( public inline fun Toml.partiallyDecodeFromStream( stream: InputStream, tomlTableName: String -): T { - return partiallyDecodeFromStream(serializersModule.serializer(), stream, tomlTableName) -} \ No newline at end of file +): T = partiallyDecodeFromStream(serializersModule.serializer(), stream, tomlTableName) diff --git a/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt b/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt index 32e52590..1052f231 100644 --- a/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt +++ b/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt @@ -4,39 +4,17 @@ import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.TomlConfig import com.akuleshov7.ktoml.parsers.TomlParser import com.akuleshov7.ktoml.tree.TomlTablePrimitive -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.Serializable + import okio.source import org.junit.jupiter.api.Test + import java.io.InputStream + import kotlin.test.assertEquals +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable class StreamTests { - @Serializable - data class TestClass( - val title: String, - val owner: Owner, - val database: Database - ) - - @Serializable - data class Owner( - val name: String, - val dob: String, - val mytest: MyTest - ) - - @Serializable - data class Database( - val server: String, - ) - - @Serializable - data class MyTest( - val myserver: String, - val myotherserver: String - ) - @Test fun readParseAndDecodeStream() { val expected = TestClass( @@ -56,25 +34,6 @@ class StreamTests { ) } - // ================ - @Serializable - data class MyTableTest( - val a: A, - val d: D - ) - - @Serializable - data class A(val b: Ab, val d: InnerTest) - - @Serializable - data class Ab(val c: InnerTest) - - @Serializable - data class D(val a: InnerTest) - - @Serializable - data class InnerTest(val str: String = "Undefined") - @Test @ExperimentalSerializationApi fun testTableDiscovery() { @@ -86,22 +45,19 @@ class StreamTests { assertEquals(test, Toml().decodeFromStream(getTestDataStream("complex_toml_tables.toml"))) // ==== checking how table discovery works val parsedResult = - getTestDataStream("complex_toml_tables.toml").source().useLines { lines -> - TomlParser(TomlConfig()).parseStringsToTomlTree(lines, TomlConfig()) - } + getTestDataStream("complex_toml_tables.toml").source().useLines { lines -> + TomlParser(TomlConfig()).parseStringsToTomlTree(lines, TomlConfig()) + } assertEquals( listOf("a", "a.b.c", "a.d", "d", "d.a"), parsedResult.getRealTomlTables().map { it.fullTableName }) } - @Serializable - data class RegressionTest(val a: Long?, val b: Long, val c: Long, val d: Long?) - @ExperimentalSerializationApi @Test fun regressionCast2Test() { val parsedResult = - Toml().decodeFromStream(getTestDataStream("class_cast_regression2.toml")) + Toml().decodeFromStream(getTestDataStream("class_cast_regression2.toml")) assertEquals(RegressionTest(null, 1, 2, null), parsedResult) } @@ -109,36 +65,10 @@ class StreamTests { @Test fun regressionPartialTest() { val parsedResult = - Toml().decodeFromStream(getTestDataStream("class_cast_regression2.toml")) + Toml().decodeFromStream(getTestDataStream("class_cast_regression2.toml")) assertEquals(RegressionTest(null, 1, 2, null), parsedResult) } - - @Serializable - data class TestRegression( - val list1: List, - val general: GeneralConfig, - val list2: List, - val warn: WarnConfig, - val list3: List - ) - - @Serializable - data class GeneralConfig( - val execCmd: String? = null, - val tags: List? = null, - val description: String? = null, - val suiteName: String? = null, - val excludedTests: List? = null, - val includedTests: List? = null, - val ignoreSaveComments: Boolean? = null - ) - - @Serializable - data class WarnConfig( - val list: List - ) - @ExperimentalSerializationApi @Test fun regressionInvalidIndex() { @@ -177,15 +107,6 @@ class StreamTests { ) } - @Serializable - data class Table1(val a: Long, val b: Long) - - @Serializable - data class Table2(val c: Long, val e: Long, val d: Long) - - @Serializable - data class TwoTomlTables(val table1: Table1, val table2: Table2) - @Test fun testPartialFileDecoding() { val test = TwoTomlTables(Table1(1, 2), Table2(1, 2, 3)) @@ -213,7 +134,166 @@ class StreamTests { ) } - private fun getTestDataStream(name: String): InputStream { - return requireNotNull(StreamTests::class.java.getResourceAsStream(name)) - } -} \ No newline at end of file + private fun getTestDataStream(name: String): InputStream = requireNotNull(StreamTests::class.java.getResourceAsStream(name)) + /** + * @property title + * @property owner + * @property database + */ + @Serializable + data class TestClass( + val title: String, + val owner: Owner, + val database: Database + ) + + /** + * @property name + * @property dob + * @property mytest + */ + @Serializable + data class Owner( + val name: String, + val dob: String, + val mytest: MyTest + ) + + /** + * @property server + */ + @Serializable + data class Database( + val server: String, + ) + + /** + * @property myserver + * @property myotherserver + */ + @Serializable + data class MyTest( + val myserver: String, + val myotherserver: String + ) + + /** + * @property a + * @property d + */ + // ================ + @Serializable + data class MyTableTest( + val a: A, + val d: D + ) + + /** + * @property b + * @property d + */ + @Serializable + data class A(val b: Ab, val d: InnerTest) + + /** + * @property c + */ + @Serializable + data class Ab(val c: InnerTest) + + /** + * @property a + */ + @Serializable + data class D(val a: InnerTest) + + /** + * @property str + */ + @Serializable + data class InnerTest(val str: String = "Undefined") + + /** + * @property a + * @property b + * @property c + * @property d + */ + @Serializable + data class RegressionTest( + val a: Long?, + val b: Long, + val c: Long, + val d: Long? + ) + + /** + * @property list1 + * @property general + * @property list2 + * @property warn + * @property list3 + */ + @Serializable + data class TestRegression( + val list1: List, + val general: GeneralConfig, + val list2: List, + val warn: WarnConfig, + val list3: List + ) + + /** + * @property execCmd + * @property tags + * @property description + * @property suiteName + * @property excludedTests + * @property includedTests + * @property ignoreSaveComments + */ + @Serializable + data class GeneralConfig( + val execCmd: String? = null, + val tags: List? = null, + val description: String? = null, + val suiteName: String? = null, + val excludedTests: List? = null, + val includedTests: List? = null, + val ignoreSaveComments: Boolean? = null + ) + + /** + * @property list + */ + @Serializable + data class WarnConfig( + val list: List + ) + + /** + * @property a + * @property b + */ + @Serializable + data class Table1(val a: Long, val b: Long) + + /** + * @property c + * @property e + * @property d + */ + @Serializable + data class Table2( + val c: Long, + val e: Long, + val d: Long + ) + + /** + * @property table1 + * @property table2 + */ + @Serializable + data class TwoTomlTables(val table1: Table1, val table2: Table2) +} From 1774ab159e06cf179efd246464f9c18097e7a6b0 Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Fri, 21 Apr 2023 03:25:42 +0200 Subject: [PATCH 4/5] Merging conflicts --- README.md | 285 ++- buildSrc/build.gradle.kts | 2 +- .../main/kotlin/com/akuleshov7/Versions.kt | 6 +- .../buildutils/DiktatConfiguration.kt | 4 +- .../buildutils/PublishingConfiguration.kt | 9 +- diktat-analysis.yml | 2 + gradle.properties | 2 - gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 35 +- gradlew.bat | 15 +- kotlin-js-store/yarn.lock | 1788 ++++------------- ktoml-core/build.gradle.kts | 15 +- .../kotlin/com/akuleshov7/ktoml/Toml.kt | 87 +- .../kotlin/com/akuleshov7/ktoml/TomlConfig.kt | 112 ++ .../ktoml/annotations/TomlComments.kt | 3 + .../ktoml/annotations/TomlInlineTable.kt | 3 +- .../ktoml/annotations/TomlInteger.kt | 10 +- .../ktoml/annotations/TomlLiteral.kt | 9 +- .../ktoml/annotations/TomlMultiline.kt | 9 +- .../ktoml/decoders/TomlAbstractDecoder.kt | 190 +- .../ktoml/decoders/TomlArrayDecoder.kt | 27 +- .../ktoml/decoders/TomlMainDecoder.kt | 33 +- .../ktoml/decoders/TomlPrimitiveDecoder.kt | 6 +- .../ktoml/encoders/TomlAbstractEncoder.kt | 295 +++ .../ktoml/encoders/TomlArrayEncoder.kt | 173 ++ .../ktoml/encoders/TomlEncoderAttributes.kt | 75 + .../ktoml/encoders/TomlInlineTableEncoder.kt | 158 ++ .../ktoml/encoders/TomlMainEncoder.kt | 149 ++ .../ktoml/exceptions/TomlDecodingException.kt | 10 +- .../ktoml/exceptions/TomlEncodingException.kt | 7 + .../akuleshov7/ktoml/parsers/StringUtils.kt | 134 +- .../akuleshov7/ktoml/parsers/TomlParser.kt | 215 +- .../ktoml/tree/TomlArrayOfTables.kt | 135 -- .../com/akuleshov7/ktoml/tree/TomlFile.kt | 30 - .../akuleshov7/ktoml/tree/TomlInlineTable.kt | 105 - .../com/akuleshov7/ktoml/tree/TomlKey.kt | 69 - .../ktoml/tree/TomlKeyValueArray.kt | 42 - .../ktoml/tree/TomlKeyValuePrimitive.kt | 42 - .../com/akuleshov7/ktoml/tree/TomlTable.kt | 101 - .../ktoml/tree/TomlTablePrimitive.kt | 129 -- .../com/akuleshov7/ktoml/tree/TomlValue.kt | 575 ------ .../akuleshov7/ktoml/tree/nodes/TomlFile.kt | 33 + .../ktoml/tree/{ => nodes}/TomlNode.kt | 135 +- .../{ => nodes/other}/TomlStubEmptyNode.kt | 19 +- .../tree/{ => nodes/pairs}/TomlKeyValue.kt | 80 +- .../tree/nodes/pairs/TomlKeyValueArray.kt | 87 + .../tree/nodes/pairs/TomlKeyValuePrimitive.kt | 83 + .../ktoml/tree/nodes/pairs/keys/TomlKey.kt | 98 + .../tree/nodes/pairs/values/TomlArray.kt | 191 ++ .../nodes/pairs/values/TomlBasicString.kt | 92 + .../tree/nodes/pairs/values/TomlBoolean.kt | 21 + .../tree/nodes/pairs/values/TomlDateTime.kt | 54 + .../tree/nodes/pairs/values/TomlDouble.kt | 26 + .../nodes/pairs/values/TomlLiteralString.kt | 140 ++ .../ktoml/tree/nodes/pairs/values/TomlLong.kt | 53 + .../ktoml/tree/nodes/pairs/values/TomlNull.kt | 25 + .../tree/nodes/pairs/values/TomlValue.kt | 53 + .../tree/nodes/tables/TomlArrayOfTables.kt | 125 ++ .../tree/nodes/tables/TomlInlineTable.kt | 145 ++ .../ktoml/tree/nodes/tables/TomlTable.kt | 176 ++ .../tree/nodes/tables/TomlTablePrimitive.kt | 126 ++ .../kotlin/com/akuleshov7/ktoml/utils/Keys.kt | 35 + .../com/akuleshov7/ktoml/utils/Regexes.kt | 26 - .../ktoml/utils/SpecialCharacters.kt | 190 ++ .../com/akuleshov7/ktoml/utils/Types.kt | 36 + .../com/akuleshov7/ktoml/utils/Utils.kt | 6 +- .../ktoml/writers/IntegerRepresentation.kt | 38 +- .../akuleshov7/ktoml/writers/TomlEmitter.kt | 90 +- .../ktoml/writers/TomlStringEmitter.kt | 4 +- .../akuleshov7/ktoml/writers/TomlWriter.kt | 18 +- .../ktoml/decoders/ArrayDecoderTest.kt | 56 +- .../decoders/ArrayOfTablesDecoderTest.kt | 6 - .../ktoml/decoders/CharDecoderTest.kt | 69 + .../ktoml/decoders/CustomSerializerTest.kt | 9 +- .../ktoml/decoders/DateTimeDecoderTest.kt | 10 + .../ktoml/decoders/DecodingTypeTest.kt | 9 +- .../ktoml/decoders/DottedKeysDecoderTest.kt | 66 +- .../ktoml/decoders/GeneralDecoderTest.kt | 71 +- .../ktoml/decoders/IntegersDecoderTest.kt | 76 + .../ktoml/decoders/NullableTablesTest.kt | 3 +- .../ktoml/decoders/PrimitivesDecoderTest.kt | 280 +++ .../ktoml/decoders/ReadMeExampleTest.kt | 50 +- .../ktoml/decoders/RequiredAnnotation.kt | 24 + .../ktoml/decoders/StringsDecoderTest.kt | 161 ++ .../ktoml/decoders/SurrogateSerializerTest.kt | 7 +- .../BasicMultilineStringDecoderTest.kt | 289 +++ .../LiteralMultilineStringDecoderTest.kt | 243 +++ .../decoders/multiline/MultilineArrayTest.kt | 278 +++ .../ktoml/encoders/ArrayEncoderTest.kt | 131 ++ .../encoders/ArrayOfTablesEncoderTest.kt | 296 +++ .../ktoml/encoders/ClassEncoderTest.kt | 153 ++ .../ktoml/encoders/CommentEncoderTest.kt | 41 + .../ktoml/encoders/CustomSerializerTest.kt | 39 + .../ktoml/encoders/DateTimeEncoderTest.kt | 37 + .../ktoml/encoders/EncoderTesting.kt | 13 + .../ktoml/encoders/EncodingAnnotationTest.kt | 286 +++ .../ktoml/encoders/MapEncoderTest.kt | 106 + .../ktoml/encoders/PolymorphicEncoderTest.kt | 109 + .../ktoml/encoders/PrimitiveEncoderTest.kt | 96 + .../ktoml/encoders/ReadMeExampleTest.kt | 89 + .../ktoml/encoders/TomlDocsEncoderTest.kt | 162 ++ .../ktoml/parsers/ArraysOfTablesTest.kt | 80 +- .../ktoml/parsers/CommentsParsing.kt | 35 + .../ktoml/parsers/CommonParserTest.kt | 8 +- .../ktoml/parsers/DottedKeyParserTest.kt | 26 +- .../ktoml/parsers/StringUtilsTest.kt | 49 + .../akuleshov7/ktoml/parsers/TableFinder.kt | 4 +- .../akuleshov7/ktoml/parsers/TomlTableTest.kt | 8 +- .../ktoml/parsers/ValueParserTest.kt | 18 +- .../ktoml/writers/ArrayOfTablesWriteTest.kt | 12 +- .../ktoml/writers/CommentWriteTest.kt | 27 + .../ktoml/writers/KeyValueWriteTest.kt | 49 +- .../ktoml/writers/TableWriteTest.kt | 12 +- .../ktoml/writers/ValueWriteTest.kt | 117 +- .../com/akuleshov7/ktoml/utils/UtilsIos.kt | 15 + .../akuleshov7/ktoml/utils/UtilsSimulator.kt | 15 + .../com/akuleshov7/ktoml/utils/UtilsMacM1.kt | 17 + ktoml-file/build.gradle.kts | 48 +- .../com/akuleshov7/ktoml/file/FileUtils.kt | 12 +- .../akuleshov7/ktoml/file/TomlFileReader.kt | 33 +- .../akuleshov7/ktoml/file/TomlFileWriter.kt | 25 +- .../akuleshov7/ktoml/file/TomlSinkEmitter.kt | 4 +- .../akuleshov7/ktoml/file/Regression189.kt | 33 + .../ktoml/file/TomlFileParserTest.kt | 22 +- ...ssion2.toml => class_cast_regression.toml} | 0 .../resources/class_cast_regression1.toml | 4 - .../commonTest/resources/regression_189.toml | 5 + .../commonTest/resources/simple_example.toml | 2 +- .../com/akuleshov7/ktoml/file/FileUtilsIos.kt | 14 + .../akuleshov7/ktoml/file/FileUtilsMacM1.kt | 16 + ktoml-source/build.gradle.kts | 41 +- .../ktoml/source/TomlSourceReader.kt | 14 +- .../akuleshov7/ktoml/source/StreamTests.kt | 10 +- renovate.json | 37 + yarn.lock | 987 +++++++++ 136 files changed, 8922 insertions(+), 3446 deletions(-) create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlArrayEncoder.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlEncoderAttributes.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlInlineTableEncoder.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlMainEncoder.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlArrayOfTables.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlFile.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlInlineTable.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKey.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValueArray.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValuePrimitive.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlTable.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlTablePrimitive.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlFile.kt rename ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/{ => nodes}/TomlNode.kt (69%) rename ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/{ => nodes/other}/TomlStubEmptyNode.kt (51%) rename ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/{ => nodes/pairs}/TomlKeyValue.kt (66%) create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValueArray.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValuePrimitive.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/keys/TomlKey.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlArray.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBasicString.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBoolean.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDateTime.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDouble.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlLiteralString.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlLong.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlNull.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlValue.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlArrayOfTables.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlInlineTable.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTable.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTablePrimitive.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Keys.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Regexes.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/SpecialCharacters.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Types.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CharDecoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/IntegersDecoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PrimitivesDecoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/RequiredAnnotation.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/BasicMultilineStringDecoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/LiteralMultilineStringDecoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/MultilineArrayTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayEncoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayOfTablesEncoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ClassEncoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/CommentEncoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/CustomSerializerTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/DateTimeEncoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/EncoderTesting.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/EncodingAnnotationTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/MapEncoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PolymorphicEncoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PrimitiveEncoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ReadMeExampleTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/TomlDocsEncoderTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/StringUtilsTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/CommentWriteTest.kt create mode 100644 ktoml-core/src/iosMain/kotlin/com/akuleshov7/ktoml/utils/UtilsIos.kt create mode 100644 ktoml-core/src/iosSimulatorArm64Main/kotlin/com/akuleshov7/ktoml/utils/UtilsSimulator.kt create mode 100644 ktoml-core/src/macosArm64Main/kotlin/com/akuleshov7/ktoml/utils/UtilsMacM1.kt create mode 100644 ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/Regression189.kt rename ktoml-file/src/commonTest/resources/{class_cast_regression2.toml => class_cast_regression.toml} (100%) delete mode 100644 ktoml-file/src/commonTest/resources/class_cast_regression1.toml create mode 100644 ktoml-file/src/commonTest/resources/regression_189.toml create mode 100644 ktoml-file/src/iosMain/kotlin/com/akuleshov7/ktoml/file/FileUtilsIos.kt create mode 100644 ktoml-file/src/macosArm64Main/kotlin/com/akuleshov7/ktoml/file/FileUtilsMacM1.kt create mode 100644 renovate.json create mode 100644 yarn.lock diff --git a/README.md b/README.md index ebb0174b..5c7b45c8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ We believe that TOML is actually the most readable and user-friendly **configura So we decided to support this format for the `kotlinx` serialization library. ## Contribution -As this young and big project [is needed](https://github.com/Kotlin/kotlinx.serialization/issues/1092) by the Kotlin community, we need your help. +As this project [is needed](https://github.com/Kotlin/kotlinx.serialization/issues/1092) by the Kotlin community, we need your help. We will be glad if you will test `ktoml` or contribute to this project. In case you don't have much time for this - at least spend 5 seconds to give us a star to attract other contributors! @@ -26,7 +26,7 @@ In case you don't have much time for this - at least spend 5 seconds to give us ## Acknowledgement Special thanks to those awesome developers who give us great suggestions, help us to maintain and improve this project: -@NightEule5, @Peanuuutz, @petertrr, @Olivki and @edrd-f. +@NightEule5, @bishiboosh, @Peanuuutz, @petertrr, @nulls, @Olivki, @edrd-f, @BOOMeranGG, @aSemy ## Supported platforms All the code is written in Kotlin **common** module. This means that it can be built for each and every Kotlin native platform. @@ -35,11 +35,12 @@ However, to reduce the scope, ktoml now supports only the following platforms: - mingwx64 - linuxx64 - macosx64 -- js (only for ktoml-core). Note, that `js(LEGACY)` is [not supported](https://github.com/Kotlin/kotlinx.serialization/issues/1448) +- ios +- js (obviously only for ktoml-core!). Note, that `js(LEGACY)` is [not supported](https://github.com/Kotlin/kotlinx.serialization/issues/1448) Other platforms could be added later on the demand (just create a corresponding issue) or easily built by users on their machines. -:globe_with_meridians: ktoml supports Kotlin 1.6 +:globe_with_meridians: ktoml supports Kotlin 1.8 ## Current limitations :heavy_exclamation_mark: Please note, that TOML standard does not define Java-like types: `Char`, `Short`, etc. @@ -47,35 +48,33 @@ You can check types that are supported in TOML [here](https://toml.io/en/v1.0.0# We will support all Kotlin primitive types in the future with the non-strict configuration of ktoml, but now only String, Long, Double and Boolean are supported from the list of Kotlin primitives. -After some brainstorming we finally decided to stream the decoded data. So we use `Sequences` instead of `Collections` in ktoml. -We think that it should give users a better performance and user-friendly API. - **General** \ We are still developing and testing this library, so it has several limitations: \ :white_check_mark: deserialization (with some parsing limitations) \ -:x: serialization (not implemented [yet](https://github.com/akuleshov7/ktoml/issues/11), less important for TOML config-files) +:white_check_mark: serialization (with tree-related limitations) -**Parsing** \ +**Parsing and decoding** \ :white_check_mark: Table sections (single and dotted) \ :white_check_mark: Key-value pairs (single and dotted) \ -:white_check_mark: Integer type \ -:white_check_mark: Float type \ -:white_check_mark: String type \ -:white_check_mark: Float type \ +:white_check_mark: Long/Integer/Byte/Short types \ +:white_check_mark: Double/Float types \ +:white_check_mark: Basic Strings \ +:white_check_mark: Literal Strings \ +:white_check_mark: Char type \ :white_check_mark: Boolean type \ :white_check_mark: Simple Arrays \ :white_check_mark: Comments \ -:white_check_mark: Literal Strings \ :white_check_mark: Inline Tables \ :white_check_mark: Offset Date-Time (to `Instant` of [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime)) \ :white_check_mark: Local Date-Time (to `LocalDateTime` of [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime)) \ :white_check_mark: Local Date (to `LocalDate` of [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime)) \ -:x: Arrays: nested; multiline; of Different Types \ -:x: Multiline Strings \ +:white_check_mark: Local Time (to `LocalTime` of [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime)) \ +:white_check_mark: Multiline Strings \ +:white_check_mark: Arrays (including multiline arrays) \ +:x: Arrays: nested; of Different Types \ :x: Nested Inline Tables \ :x: Array of Tables \ -:x: Inline Array of Tables \ -:x: Local Time +:x: Inline Array of Tables ## Dependency The library is hosted on the [Maven Central](https://search.maven.org/artifact/com.akuleshov7/ktoml-core). @@ -87,12 +86,12 @@ To import `ktoml` library you need to add following dependencies to your code: com.akuleshov7 ktoml-core - 0.2.11 + 0.4.0 com.akuleshov7 ktoml-file - 0.2.11 + 0.4.0 ``` @@ -101,8 +100,8 @@ To import `ktoml` library you need to add following dependencies to your code: Gradle Groovy ```groovy -implementation 'com.akuleshov7:ktoml-core:0.2.11' -implementation 'com.akuleshov7:ktoml-file:0.2.11' +implementation 'com.akuleshov7:ktoml-core:0.4.0' +implementation 'com.akuleshov7:ktoml-file:0.4.0' ``` @@ -110,8 +109,8 @@ implementation 'com.akuleshov7:ktoml-file:0.2.11' Gradle Kotlin ```kotlin -implementation("com.akuleshov7:ktoml-core:0.2.11") -implementation("com.akuleshov7:ktoml-file:0.2.11") +implementation("com.akuleshov7:ktoml-core:0.4.0") +implementation("com.akuleshov7:ktoml-file:0.4.0") ``` @@ -178,6 +177,34 @@ val resultFromList = TomlFileReader.partiallyDecodeFromFile(serializer( ``` +**Serialization:** +
+Straight-forward serialization + +```kotlin +// add extensions from 'kotlinx' lib to your project: +import kotlinx.serialization.encodeFromString +// add com.akuleshov7:ktoml-core to your project: +import com.akuleshov7.ktoml.Toml + +@Serializable +data class MyClass(/* your fields */) + +val toml = Toml.decodeFromString(MyClass(/* ... */)) +``` +
+ +
+Toml File serialization + +```kotlin +// add com.akuleshov7:ktoml-file to your project +import com.akuleshov7.ktoml.file.TomlFileWriter + +TomlFileWriter.encodeToFile(serializer(), /* file path to toml file */) +``` +
+ **Parser to AST:**
Simple parser @@ -186,8 +213,8 @@ val resultFromList = TomlFileReader.partiallyDecodeFromFile(serializer( import com.akuleshov7.ktoml.parsers.TomlParser import com.akuleshov7.ktoml.TomlConfig /* ========= */ -var tomlAST = TomlParser(TomlConfig()).parseStringsToTomlTree(/* list with toml strings */) -tomlAST = TomlParser(TomlConfig()).parseString(/* the string that you want to parse */) +var tomlAST = TomlParser(TomlInputConfig()).parseStringsToTomlTree(/* list with toml strings */) +tomlAST = TomlParser(TomlInputConfig()).parseString(/* the string that you want to parse */) tomlAST.prettyPrint() ```
@@ -198,7 +225,7 @@ special configuration class that can be passed to the decoder method: ```kotlin Toml( - config = TomlConfig( + inputConfig = TomlInputConfig( // allow/prohibit unknown names during the deserialization, default false ignoreUnknownNames = false, // allow/prohibit empty values like "a = # comment", default true @@ -209,6 +236,8 @@ Toml( allowEscapedQuotesInLiteralStrings = true, // allow/prohibit processing of empty toml, if false - throws an InternalDecodingException exception, default is true allowEmptyToml = true, + ), + outputConfig = TomlOutputConfig( // indentation symbols for serialization, default 4 spaces indentation = Indentation.FOUR_SPACES, ) @@ -223,36 +252,38 @@ Ktoml will produce different exceptions in case of the invalid input. Please not `TomlDecodingException` and `TomlEncodingException` - you can catch them in your code. All other exceptions inherit one of these two and will not be public. ## How ktoml works: examples +:heavy_exclamation_mark: You can check how below examples work in [decoding ReadMeExampleTest](https://github.com/akuleshov7/ktoml/blob/main/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt) and [encoding ReadMeExampleTest](https://github.com/akuleshov7/ktoml/blob/main/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ReadMeExampleTest.kt). -This tool natively deserializes toml expressions using native Kotlin compiler plug-in and [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serialization-guide.md). - +
+Deserialization The following example: + ```toml someBooleanProperty = true # inline tables in gradle 'libs.versions.toml' notation gradle-libs-like-property = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } [table1] -# it can be null or nil, but don't forget to mark it with '?' in the codes -# keep in mind, that null is prohibited by TOML spec, but it is very important in Kotlin -# see allowNullValues for a more strict enforcement of the TOML spec -property1 = null -property2 = 6 -# check property3 in Table1 below. As it has the default value, it is not required and can be not provided - -[table2] -someNumber = 5 - [table2."akuleshov7.com"] - name = 'this is a "literal" string' - # empty lists are also supported - configurationList = ["a", "b", "c", null] - -# such redeclaration of table2 -# is prohibited in toml specification; -# but ktoml is allowing it in non-strict mode: -[table2] -otherNumber = 5.56 + # null is prohibited by the TOML spec, but allowed in ktoml for nullable types + # so for 'property1' null value is ok. Use: property1 = null + property1 = 100 + property2 = 6 +[table2] + someNumber = 5 +[table2."akuleshov7.com"] + name = 'this is a "literal" string' + # empty lists are also supported + configurationList = ["a", "b", "c"] + + # such redeclaration of table2 + # is prohibited in toml specification; + # but ktoml is allowing it in non-strict mode: + [table2] + otherNumber = 5.56 + # use single quotes + charFromString = 'a' + charFromInteger = 123 ``` can be deserialized to `MyClass`: @@ -262,26 +293,38 @@ data class MyClass( val someBooleanProperty: Boolean, val table1: Table1, val table2: Table2, - @SerialName("gradle-libs-like-property") - val kotlinJvm: GradlePlugin + @SerialName("gradle-libs-like-property") + val kotlinJvm: GradlePlugin ) @Serializable data class Table1( - // nullable values, from toml you can pass null/nil/empty value to this kind of a field + // nullable property, from toml input you can pass "null"/"nil"/"empty" value (no quotes needed) to this field val property1: Long?, - // please note, that according to the specification of toml integer values should be represented with Long - val property2: Long, - // no need to pass this value as it has the default value and is NOT REQUIRED - val property3: Long = 5 + // please note, that according to the specification of toml integer values should be represented with Long, + // but we allow to use Int/Short/etc. Just be careful with overflow + val property2: Byte, + // no need to pass this value in the input as it has the default value and so it is NOT REQUIRED + val property3: Short = 5 ) @Serializable data class Table2( val someNumber: Long, @SerialName("akuleshov7.com") - val inlineTable: InlineTable, - val otherNumber: Double + val inlineTable: NestedTable, + val otherNumber: Double, + // Char in a manner of Java/Kotlin is not supported in TOML, because single quotes are used for literal strings. + // However, ktoml supports reading Char from both single-char string and from it's integer code + val charFromString: Char, + val charFromInteger: Char +) + +@Serializable +data class NestedTable( + val name: String, + @SerialName("configurationList") + val overriddenName: List ) @Serializable @@ -289,7 +332,6 @@ data class GradlePlugin(val id: String, val version: Version) @Serializable data class Version(val ref: String) - ``` with the following code: @@ -302,30 +344,135 @@ Translation of the example above to json-terminology: ```json { "someBooleanProperty": true, + + "gradle-libs-like-property": { + "id": "org.jetbrains.kotlin.jvm", + "version": { + "ref": "kotlin" + } + }, + "table1": { - "property1": 5, + "property1": 100, "property2": 5 }, "table2": { "someNumber": 5, + + "otherNumber": 5.56, "akuleshov7.com": { "name": "my name", "configurationList": [ "a", "b", "c" - ], - "otherNumber": 5.56 - } - }, - "gradle-libs-like-property": { - "id": "org.jetbrains.kotlin.jvm", - "version": { - "ref": "kotlin" + ] } } } - ``` +
+ +
+Serialization +The following example from above: -:heavy_exclamation_mark: You can check how this example works in [ReadMeExampleTest](https://github.com/akuleshov7/ktoml/blob/main/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt). +```toml +someBooleanProperty = true +# inline tables in gradle 'libs.versions.toml' notation +gradle-libs-like-property = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } + +[table1] +# null is prohibited by the TOML spec, but allowed in ktoml for nullable types +# so for 'property1' null value is ok. Use: property1 = null. +# Null can also be prohibited with 'allowNullValues = false' +property1 = 100 +property2 = 6 + +[table2] + someNumber = 5 + [table2."akuleshov7.com"] + name = 'this is a "literal" string' + # empty lists are also supported + configurationList = ["a", "b", "c"] + +# such redeclaration of table2 +# is prohibited in toml specification; +# but ktoml is allowing it in non-strict mode: +[table2] + otherNumber = 5.56 + # use single quotes + charFromString = 'a' + charFromInteger = 123 +``` + +can be serialized from `MyClass`: + +```kotlin +@Serializable +data class MyClass( + val someBooleanProperty: Boolean, + @TomlComments( + "Comments can be added", + "More comments can also be added" + ) + val table1: Table1, + val table2: Table2, + @SerialName("gradle-libs-like-property") + val kotlinJvm: GradlePlugin +) + +@Serializable +data class Table1( + @TomlComments(inline = "At the end of lines too") + // nullable values, represented as "null" in toml. For more strict behavior, + // null values can be ignored with the ignoreNullValues config property. + val property1: Long?, + // please note, that according to the specification of toml integer values should be represented with Long + val property2: Long, + // Default values can be ignored with the ignoreDefaultValues config property. + val property3: Long = 5 +) + +@Serializable +data class Table2( + // Integers can be formatted in hex, binary, etc. Currently only decimal is + // supported. + @TomlInteger(IntegerRepresentation.DECIMAL) + val someNumber: Long, + @SerialName("akuleshov7.com") + @TomlInlineTable // Can be on the property + val inlineTable: InlineTable, + @TomlComments( + "Properties always appear before sub-tables, tables aren't redeclared" + ) + val otherNumber: Double +) + +@Serializable +data class InlineTable( + @TomlLiteral + val name: String, + @SerialName("configurationList") + val overriddenName: List +) + +@Serializable +@TomlInlineTable // ...or the class +data class GradlePlugin( + val id: String, + // version is "collapsed": single member inline tables become dotted pairs. + val version: Version +) + +@Serializable +@TomlInlineTable +data class Version(val ref: String) +``` + +with the following code: + +```kotlin +Toml.encodeToString(/* your encoded object */) +``` +
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 2b099910..c5995fdb 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,7 +8,7 @@ repositories { dependencies { // this hack prevents the following bug: https://github.com/gradle/gradle/issues/9770 - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20") implementation("org.cqfn.diktat:diktat-gradle-plugin:1.1.0") implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.15.0") diff --git a/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt b/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt index a8baff4d..ba71fe0f 100644 --- a/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt +++ b/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt @@ -4,8 +4,8 @@ "PACKAGE_NAME_INCORRECT_PATH") object Versions { - const val KOTLIN = "1.6.21" + const val KOTLIN = "1.8.0" const val JUNIT = "5.7.1" - const val OKIO = "3.0.0" - const val SERIALIZATION = "1.3.2" + const val OKIO = "3.1.0" + const val SERIALIZATION = "1.4.1" } diff --git a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt b/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt index 99ea55a3..02e9c1b1 100644 --- a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt +++ b/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt @@ -30,10 +30,10 @@ fun Project.configureDiktat() { // using `Project#path` here, because it must be unique in gradle's project hierarchy if (path == rootProject.path) { include("$rootDir/buildSrc/src/**/*.kt", "$rootDir/*.kts", "$rootDir/buildSrc/**/*.kts") - exclude("src/test/**/*.kt", "src/commonTest/**/*.kt", "src/jvmTest/**/*.kt") // path matching this pattern will not be checked by diktat + exclude("src/test/**/*.kt", "src/commonTest/**/*.kt") // path matching this pattern will not be checked by diktat } else { include("src/**/*.kt", "**/*.kts") - exclude("src/**test/**/*.kt", "src/commonTest/**/*.kt", "src/jvmTest/**/*.kt") // path matching this pattern will not be checked by diktat + exclude("src/**test/**/*.kt", "src/commonTest/**/*.kt") // path matching this pattern will not be checked by diktat } } } diff --git a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt b/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt index d238c764..856aeebe 100644 --- a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt +++ b/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt @@ -56,7 +56,14 @@ fun Project.configurePublishing() { // https://kotlinlang.org/docs/mpp-publish-lib.html#avoid-duplicate-publications // `configureNexusPublishing` adds sonatype publication tasks inside `afterEvaluate`. afterEvaluate { - val publicationsFromMainHost = listOf("jvm", "js", "linuxX64", "mingwX64", "kotlinMultiplatform", "metadata") + val publicationsFromMainHost = listOf( + "jvm", + "js", + "linuxX64", + "mingwX64", + "kotlinMultiplatform", + "metadata" + ) configure { publications.matching { it.name in publicationsFromMainHost }.all { val targetPublication = this@all diff --git a/diktat-analysis.yml b/diktat-analysis.yml index 90412414..6523cc36 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -5,6 +5,8 @@ kotlinVersion: 1.4 srcDirectories: "main,nativeMain" testDirs: "test,nativeTest,commonTest" +- name: PACKAGE_NAME_INCORRECT_PATH + enabled: false - name: MISSING_KDOC_ON_FUNCTION enabled: false - name: MISSING_KDOC_CLASS_ELEMENTS diff --git a/gradle.properties b/gradle.properties index d3e03f9b..f61da98c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,6 @@ group=com.akuleshov7 description="TOML serialization library for Kotlin language (including Kotlin Native, js, jvm)" kotlin.code.style=official -kotlin.mpp.enableGranularSourceSetsMetadata=true -kotlin.native.enableDependencyPropagation=false kotlin.mpp.stability.nowarn=true # gradle performance org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 40133 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/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32..93e3f59f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 96c65450..8c3b4d32 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -2,257 +2,155 @@ # yarn lockfile v1 -"@discoveryjs/json-ext@^0.5.0": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" - integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@babel/code-frame@^7.10.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" + integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== + dependencies: + "@babel/highlight" "^7.18.6" -"@js-joda/core@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" - integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== +"@babel/helper-validator-identifier@^7.18.6": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@types/component-emitter@^1.2.10": - version "1.2.11" - resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.11.tgz#50d47d42b347253817a39709fef03ce66a108506" - integrity sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ== +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" -"@types/cookie@^0.4.0": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" - integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" -"@types/cors@^2.8.8": - version "2.8.12" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" - integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"@types/eslint-scope@^3.7.0": - version "3.7.3" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" - integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda" + integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg== dependencies: - "@types/eslint" "*" - "@types/estree" "*" + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" -"@types/eslint@*": - version "8.4.2" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.2.tgz#48f2ac58ab9c631cb68845c3d956b28f79fad575" - integrity sha512-Z1nseZON+GEnFjJc04sv4NSALGjhFwy6K0HXt7qsn5ArfAKtb63dXNJHf+1YW6IpOIYRBGUbu3GwJdj8DGnCjA== +"@jridgewell/sourcemap-codec@1.4.14": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.18" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" + integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== dependencies: - "@types/estree" "*" - "@types/json-schema" "*" + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + +"@js-joda/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" + integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== + +"@rollup/plugin-commonjs@^21.0.1": + version "21.1.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-21.1.0.tgz#45576d7b47609af2db87f55a6d4b46e44fc3a553" + integrity sha512-6ZtHx3VHIp2ReNNDxHjuUml6ur+WcQ28N1yHgCQwsbNkQg2suhxGMDQGJOn/KuDxKtd1xuZP5xSTwBA4GQ8hbA== + dependencies: + "@rollup/pluginutils" "^3.1.0" + commondir "^1.0.1" + estree-walker "^2.0.1" + glob "^7.1.6" + is-reference "^1.2.1" + magic-string "^0.25.7" + resolve "^1.17.0" + +"@rollup/plugin-node-resolve@^13.1.3": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz#da1c5c5ce8316cef96a2f823d111c1e4e498801c" + integrity sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw== + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + deepmerge "^4.2.2" + is-builtin-module "^3.1.0" + is-module "^1.0.0" + resolve "^1.19.0" + +"@rollup/plugin-typescript@^8.3.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.5.0.tgz#7ea11599a15b0a30fa7ea69ce3b791d41b862515" + integrity sha512-wMv1/scv0m/rXx21wD2IsBbJFba8wGF3ErJIr6IKRfRj49S85Lszbxb4DCo8iILpluTjk2GAAu9CoZt4G3ppgQ== + dependencies: + "@rollup/pluginutils" "^3.1.0" + resolve "^1.17.0" + +"@rollup/pluginutils@^3.0.9", "@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" "@types/estree@*": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" + integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== + +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== -"@types/estree@^0.0.50": - version "0.0.50" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" - integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/node@*": + version "18.15.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.12.tgz#833756634e78c829e1254db006468dadbb0c696b" + integrity sha512-Wha1UwsB3CYdqUm2PPzh/1gujGCNtWVUYF0mB00fJFoR4gTyWTDPjSm+zBF787Ahw8vSGgBja90MkgFwvB86Dg== -"@types/json-schema@*", "@types/json-schema@^7.0.8": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/node@^12.12.14": + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== -"@types/node@*", "@types/node@>=10.0.0": - version "17.0.31" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.31.tgz#a5bb84ecfa27eec5e1c802c6bbf8139bdb163a5d" - integrity sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q== +"@types/resolve@1.17.1": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" + integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== + dependencies: + "@types/node" "*" "@ungap/promise-all-settled@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== - -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== - -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== - -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== - -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@xtuc/long" "4.2.2" - -"@webpack-cli/configtest@^1.1.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.1.1.tgz#9f53b1b7946a6efc2a749095a4f450e2932e8356" - integrity sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg== - -"@webpack-cli/info@^1.4.0": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.4.1.tgz#2360ea1710cbbb97ff156a3f0f24556e0fc1ebea" - integrity sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA== - dependencies: - envinfo "^7.7.3" - -"@webpack-cli/serve@^1.6.0": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.6.1.tgz#0de2875ac31b46b6c5bb1ae0a7d7f0ba5678dffe" - integrity sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -abab@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - -accepts@~1.3.4: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-import-assertions@^1.7.6: - version "1.8.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" - integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== - -acorn@^8.4.1, acorn@^8.5.0: - version "8.7.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" - integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== - -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" +acorn@^8.5.0: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== ansi-colors@4.1.1: version "4.1.1" @@ -264,6 +162,13 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -272,9 +177,9 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: color-convert "^2.0.1" anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" @@ -284,44 +189,21 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-arraybuffer@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" - integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= - -base64id@2.0.0, base64id@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" - integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -body-parser@^1.19.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.10.3" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -330,7 +212,14 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.2, braces@~3.0.2: +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -342,44 +231,29 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.14.5: - version "4.20.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf" - integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg== - dependencies: - caniuse-lite "^1.0.30001332" - electron-to-chromium "^1.4.118" - escalade "^3.1.1" - node-releases "^2.0.3" - picocolors "^1.0.0" - buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== camelcase@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001332: - version "1.0.30001335" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001335.tgz#899254a0b70579e5a957c32dced79f0727c61f2a" - integrity sha512-ddP1Tgm7z2iIxu6QTtbZUv6HJxSaV/PZeSrWFZtbY4JZ69tOeNhBCl3HyRQgeNZKE5AOn1kpV7fhljigy0Ty3w== +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" chalk@^4.1.0: version "4.1.2" @@ -389,22 +263,7 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" - integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chokidar@^3.5.1: +chokidar@3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -419,11 +278,6 @@ chokidar@^3.5.1: optionalDependencies: fsevents "~2.3.2" -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== - cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -433,14 +287,12 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" + color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" @@ -449,103 +301,32 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^2.0.14: - version "2.0.16" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" - integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== - -colors@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -component-emitter@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -connect@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" - integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== - dependencies: - debug "2.6.9" - finalhandler "1.1.2" - parseurl "~1.3.3" - utils-merge "1.0.1" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -cookie@~0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== - -cors@~2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -custom-event@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" - integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -date-format@^4.0.9: - version "4.0.9" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.9.tgz#4788015ac56dedebe83b03bc361f00c1ddcf1923" - integrity sha512-+8J+BOUpSrlKLQLeF8xJJVTxS8QfRSuJgwxSVvslzgO3E6khbI0F5mMEPf5mTYhCCm4h99knYP6H3W9n3BQFrg== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - -debug@^4.3.4, debug@~4.3.1: +debug@4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -557,183 +338,58 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +decode-uri-component@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -di@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" - integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw= +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== diff@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== -dom-serialize@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" - integrity sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs= +dukat@0.5.8-rc.4: + version "0.5.8-rc.4" + resolved "https://registry.yarnpkg.com/dukat/-/dukat-0.5.8-rc.4.tgz#90384dcb50b14c26f0e99dae92b2dea44f5fce21" + integrity sha512-ZnMt6DGBjlVgK2uQamXfd7uP/AxH7RqI0BL9GLrrJb2gKdDxvJChWy+M9AQEaL+7/6TmxzJxFOsRiInY9oGWTA== dependencies: - custom-event "~1.0.0" - ent "~2.2.0" - extend "^3.0.0" - void-elements "^2.0.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -electron-to-chromium@^1.4.118: - version "1.4.132" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.132.tgz#b64599eb018221e52e2e4129de103b03a413c55d" - integrity sha512-JYdZUw/1068NWN+SwXQ7w6Ue0bWYGihvSUNNQwurvcDV/SM7vSiGZ3NuFvFgoEiCs4kB8xs3cX2an3wB7d4TBw== + google-protobuf "3.12.2" + typescript "3.9.5" emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - -engine.io-parser@~4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-4.0.3.tgz#83d3a17acfd4226f19e721bb22a1ee8f7662d2f6" - integrity sha512-xEAAY0msNnESNPc00e19y5heTPX4y/TJ36gr8t1voOaNmTojP9b3oK3BbJLFufW2XFPQaaijpFewm2g2Um3uqA== - dependencies: - base64-arraybuffer "0.1.4" - -engine.io@~4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-4.1.2.tgz#f96ceb56d4b39cc7ca5bd29a20e9c99c1ad1a765" - integrity sha512-t5z6zjXuVLhXDMiFJPYsPOWEER8B0tIsD3ETgw19S1yg9zryvUfY3Vhtk3Gf4sihw/bQGIqQ//gjvVlu+Ca0bQ== - dependencies: - accepts "~1.3.4" - base64id "2.0.0" - cookie "~0.4.1" - cors "~2.8.5" - debug "~4.3.1" - engine.io-parser "~4.0.0" - ws "~7.4.2" - -enhanced-resolve@^5.8.3: - version "5.9.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88" - integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -ent@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= - -envinfo@^7.7.3: - version "7.8.1" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" - integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== - -es-module-lexer@^0.9.0: - version "0.9.3" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" - integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - escape-string-regexp@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== -fastest-levenshtein@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" - integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== +estree-walker@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== fill-range@^7.0.1: version "7.0.1" @@ -742,19 +398,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - find-up@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -763,47 +406,20 @@ find-up@5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^3.2.5: - version "3.2.5" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" - integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== - -follow-redirects@^1.0.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4" - integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ== - -format-util@1.0.5: +format-util@1.0.5, format-util@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== -fs-extra@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@~2.3.2: version "2.3.2" @@ -820,20 +436,6 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -841,15 +443,10 @@ glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@7.1.7: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== +glob@7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -858,38 +455,33 @@ glob@7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.3, glob@^7.1.7: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== +glob@^7.1.3, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +google-protobuf@3.12.2: + version "3.12.2" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.12.2.tgz#50ce9f9b6281235724eb243d6a83e969a2176e53" + integrity sha512-4CZhpuRr1d6HjlyrxoXoocoGFnRYgKULgMtikMddA9ztRyYR59Aondv2FioyxWVamRo0rF2XpYawkTCBEQOSkA== -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -902,71 +494,19 @@ he@1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.6.2: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4: +inherits@2: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -interpret@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" - integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -974,17 +514,29 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-core-module@^2.8.1: - version "2.9.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== +is-builtin-module@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + +is-core-module@^2.11.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" + integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== dependencies: has "^1.0.3" +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -998,6 +550,11 @@ is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -1008,46 +565,38 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== +is-reference@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" + integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== dependencies: - isobject "^3.0.1" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + "@types/estree" "*" is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -isbinaryfile@^4.0.8: - version "4.0.10" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" - integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== +jest-worker@^26.2.1: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== dependencies: "@types/node" "*" merge-stream "^2.0.0" - supports-color "^8.0.0" + supports-color "^7.0.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@4.1.0: version "4.1.0" @@ -1056,101 +605,6 @@ js-yaml@4.1.0: dependencies: argparse "^2.0.1" -json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -karma-chrome-launcher@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz#805a586799a4d05f4e54f72a204979f3f3066738" - integrity sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg== - dependencies: - which "^1.2.1" - -karma-mocha@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.1.tgz#4b0254a18dfee71bdbe6188d9a6861bf86b0cd7d" - integrity sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ== - dependencies: - minimist "^1.2.3" - -karma-sourcemap-loader@0.3.8: - version "0.3.8" - resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.8.tgz#d4bae72fb7a8397328a62b75013d2df937bdcf9c" - integrity sha512-zorxyAakYZuBcHRJE+vbrK2o2JXLFWK8VVjiT/6P+ltLBUGUvqTEkUiQ119MGdOrK7mrmxXHZF1/pfT6GgIZ6g== - dependencies: - graceful-fs "^4.1.2" - -karma-webpack@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.0.tgz#2a2c7b80163fe7ffd1010f83f5507f95ef39f840" - integrity sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA== - dependencies: - glob "^7.1.3" - minimatch "^3.0.4" - webpack-merge "^4.1.5" - -karma@6.3.4: - version "6.3.4" - resolved "https://registry.yarnpkg.com/karma/-/karma-6.3.4.tgz#359899d3aab3d6b918ea0f57046fd2a6b68565e6" - integrity sha512-hbhRogUYIulfkBTZT7xoPrCYhRBnBoqbbL4fszWD0ReFGUxU+LYBr3dwKdAluaDQ/ynT9/7C+Lf7pPNW4gSx4Q== - dependencies: - body-parser "^1.19.0" - braces "^3.0.2" - chokidar "^3.5.1" - colors "^1.4.0" - connect "^3.7.0" - di "^0.0.1" - dom-serialize "^2.2.1" - glob "^7.1.7" - graceful-fs "^4.2.6" - http-proxy "^1.18.1" - isbinaryfile "^4.0.8" - lodash "^4.17.21" - log4js "^6.3.0" - mime "^2.5.2" - minimatch "^3.0.4" - qjobs "^1.2.0" - range-parser "^1.2.1" - rimraf "^3.0.2" - socket.io "^3.1.0" - source-map "^0.6.1" - tmp "^0.2.1" - ua-parser-js "^0.7.28" - yargs "^16.1.1" - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -1158,16 +612,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= - -lodash@^4.17.15, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - log-symbols@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -1176,103 +620,60 @@ log-symbols@4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -log4js@^6.3.0: - version "6.4.6" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.4.6.tgz#1878aa3f09973298ecb441345fe9dd714e355c15" - integrity sha512-1XMtRBZszmVZqPAOOWczH+Q94AI42mtNWjvjA5RduKTSWjEc56uOBbyM1CJnfN4Ym0wSd8cQ43zOojlSHgRDAw== +magic-string@^0.25.7: + version "0.25.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== dependencies: - date-format "^4.0.9" - debug "^4.3.4" - flatted "^3.2.5" - rfdc "^1.3.0" - streamroller "^3.0.8" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + sourcemap-codec "^1.4.8" merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@^2.5.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== dependencies: - brace-expansion "^1.1.7" + brace-expansion "^2.0.1" -minimatch@^3.0.4: +minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -minimist@^1.2.3: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -mocha@9.1.2: - version "9.1.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.1.2.tgz#93f53175b0f0dc4014bd2d612218fccfcf3534d3" - integrity sha512-ta3LtJ+63RIBP03VBjMGtSqbe6cWXRejF9SyM9Zyli1CKZJZ+vfCTj3oW24V7wAphMJdpOFLoMI3hjJ1LWbs0w== +mocha@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" + integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" browser-stdout "1.3.1" - chokidar "3.5.2" - debug "4.3.2" + chokidar "3.5.3" + debug "4.3.4" diff "5.0.0" escape-string-regexp "4.0.0" find-up "5.0.0" - glob "7.1.7" - growl "1.10.5" + glob "7.2.0" he "1.2.0" js-yaml "4.1.0" log-symbols "4.1.0" - minimatch "3.0.4" + minimatch "5.0.1" ms "2.1.3" - nanoid "3.1.25" + nanoid "3.3.3" serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" - which "2.0.2" - workerpool "6.1.5" + workerpool "6.2.1" yargs "16.2.0" yargs-parser "20.2.4" yargs-unparser "2.0.0" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -1283,83 +684,23 @@ ms@2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@3.1.25: - version "3.1.25" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" - integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -node-releases@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476" - integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ== +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -object-assign@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-inspect@^1.9.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" - integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -1367,13 +708,6 @@ p-limit@^3.0.2: dependencies: yocto-queue "^0.1.0" -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - p-locate@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" @@ -1381,16 +715,6 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -1399,52 +723,18 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qjobs@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" - integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== - -qs@6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -1452,21 +742,6 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -range-parser@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -1474,172 +749,80 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -rechoir@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" - integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== - dependencies: - resolve "^1.9.0" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -resolve@^1.9.0: - version "1.22.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" - integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== +resolve@^1.17.0, resolve@^1.19.0: + version "1.22.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== dependencies: - is-core-module "^2.8.1" + is-core-module "^2.11.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" +rollup-plugin-sourcemaps@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz#bf93913ffe056e414419607f1d02780d7ece84ed" + integrity sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw== + dependencies: + "@rollup/pluginutils" "^3.0.9" + source-map-resolve "^0.6.0" + +rollup-plugin-terser@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" + integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ== + dependencies: + "@babel/code-frame" "^7.10.4" + jest-worker "^26.2.1" + serialize-javascript "^4.0.0" + terser "^5.0.0" + +rollup@^2.68.0: + version "2.79.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" + integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== + optionalDependencies: + fsevents "~2.3.2" + safe-buffer@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -schema-utils@^3.1.0, schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -serialize-javascript@6.0.0, serialize-javascript@^6.0.0: +serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== dependencies: randombytes "^2.1.0" -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -socket.io-adapter@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz#edc5dc36602f2985918d631c1399215e97a1b527" - integrity sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg== - -socket.io-parser@~4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0" - integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g== - dependencies: - "@types/component-emitter" "^1.2.10" - component-emitter "~1.3.0" - debug "~4.3.1" - -socket.io@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-3.1.2.tgz#06e27caa1c4fc9617547acfbb5da9bc1747da39a" - integrity sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw== - dependencies: - "@types/cookie" "^0.4.0" - "@types/cors" "^2.8.8" - "@types/node" ">=10.0.0" - accepts "~1.3.4" - base64id "~2.0.0" - debug "~4.3.1" - engine.io "~4.1.0" - socket.io-adapter "~2.1.0" - socket.io-parser "~4.0.3" - -source-map-js@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" - integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== - -source-map-loader@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-3.0.0.tgz#f2a04ee2808ad01c774dea6b7d2639839f3b3049" - integrity sha512-GKGWqWvYr04M7tn8dryIWvb0s8YM41z82iQv01yBtIylgxax0CwvSy6gc2Y02iuXwEfGWRlMicH0nvms9UZphw== +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== dependencies: - abab "^2.0.5" - iconv-lite "^0.6.2" - source-map-js "^0.6.2" + randombytes "^2.1.0" -source-map-support@0.5.20: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== +source-map-resolve@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" + integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" + atob "^2.1.2" + decode-uri-component "^0.2.0" -source-map-support@~0.5.20: +source-map-support@0.5.21, source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -1647,36 +830,15 @@ source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0, source-map@^0.6.1: +source-map@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@~0.8.0-beta.0: - version "0.8.0-beta.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" - integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== - dependencies: - whatwg-url "^7.0.0" - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - -streamroller@^3.0.8: - version "3.0.8" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.0.8.tgz#84b190e4080ee311ca1ebe0444e30ac8eedd028d" - integrity sha512-VI+ni3czbFZrd1MrlybxykWZ8sMDCMtTU7YJyhgb9M5X6d1DDxLdJr+gSnmRpXPMnIWxWKMaAE8K0WumBp3lDg== - dependencies: - date-format "^4.0.9" - debug "^4.3.4" - fs-extra "^10.1.0" +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" @@ -1694,24 +856,26 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - strip-json-comments@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@8.1.1, supports-color@^8.0.0: +supports-color@8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" -supports-color@^7.1.0: +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -1723,39 +887,16 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tapable@^2.1.1, tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -terser-webpack-plugin@^5.1.3: - version "5.3.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz#0320dcc270ad5372c1e8993fabbd927929773e54" - integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g== - dependencies: - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.0" - source-map "^0.6.1" - terser "^5.7.2" - -terser@^5.7.2: - version "5.13.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.13.1.tgz#66332cdc5a01b04a224c9fad449fc1a18eaa1799" - integrity sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA== +terser@^5.0.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.1.tgz#948f10830454761e2eeedc6debe45c532c83fd69" + integrity sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw== dependencies: + "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" commander "^2.20.0" - source-map "~0.8.0-beta.0" source-map-support "~0.5.20" -tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -1763,182 +904,30 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +tslib@^2.3.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== -tr46@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" - integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= - dependencies: - punycode "^2.1.0" +typescript@3.9.5: + version "3.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" + integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" +typescript@4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== -ua-parser-js@^0.7.28: - version "0.7.31" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" - integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ== +typescript@^3.7.2: + version "3.9.10" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" + integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== -universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - -v8-compile-cache@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== - -vary@^1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -void-elements@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= - -watchpack@^2.2.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" - integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== - -webpack-cli@4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.0.tgz#dc43e6e0f80dd52e89cbf73d5294bcd7ad6eb343" - integrity sha512-n/jZZBMzVEl4PYIBs+auy2WI0WTQ74EnJDiyD98O2JZY6IVIHJNitkYp/uTXOviIOMfgzrNvC9foKv/8o8KSZw== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^1.1.0" - "@webpack-cli/info" "^1.4.0" - "@webpack-cli/serve" "^1.6.0" - colorette "^2.0.14" - commander "^7.0.0" - execa "^5.0.0" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^2.2.0" - rechoir "^0.7.0" - v8-compile-cache "^2.2.0" - webpack-merge "^5.7.3" - -webpack-merge@^4.1.5: - version "4.2.2" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" - integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== - dependencies: - lodash "^4.17.15" - -webpack-merge@^5.7.3: - version "5.8.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" - integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== - dependencies: - clone-deep "^4.0.1" - wildcard "^2.0.0" - -webpack-sources@^3.2.0: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -webpack@5.57.1: - version "5.57.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.57.1.tgz#ead5ace2c17ecef2ae8126f143bfeaa7f55eab44" - integrity sha512-kHszukYjTPVfCOEyrUthA3jqJwduY/P3eO8I0gMNOZGIQWKAwZftxmp5hq6paophvwo9NoUrcZOecs9ulOyyTg== - dependencies: - "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.50" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.4.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.8.3" - es-module-lexer "^0.9.0" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" - json-parse-better-errors "^1.0.2" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.1.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.2.0" - webpack-sources "^3.2.0" - -whatwg-url@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" - integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - -which@2.0.2, which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -which@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== - -workerpool@6.1.5: - version "6.1.5" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581" - integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw== +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== wrap-ansi@^7.0.0: version "7.0.0" @@ -1952,12 +941,7 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -ws@~7.4.2: - version "7.4.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== y18n@^5.0.5: version "5.0.8" @@ -1984,7 +968,7 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@16.2.0, yargs@^16.1.1: +yargs@16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== diff --git a/ktoml-core/build.gradle.kts b/ktoml-core/build.gradle.kts index f5a53a68..a1834d5b 100644 --- a/ktoml-core/build.gradle.kts +++ b/ktoml-core/build.gradle.kts @@ -11,6 +11,10 @@ plugins { kotlin { explicitApi() + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(8)) + } + js(IR) { browser() nodejs() @@ -19,15 +23,16 @@ kotlin { // building jvm task only on windows jvm { compilations.all { - kotlinOptions { - jvmTarget = "11" - } + kotlinOptions.jvmTarget = "1.8" } } mingwX64() linuxX64() macosX64() + macosArm64() + ios() + iosSimulatorArm64() sourceSets { all { @@ -37,7 +42,7 @@ kotlin { val commonMain by getting { dependencies { api("org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.SERIALIZATION}") - api("org.jetbrains.kotlinx:kotlinx-datetime:0.3.2") + api("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") } } @@ -75,7 +80,7 @@ tasks.withType { } tasks.withType { - if (this.name.contains("testTask")) { + if (this.name.contains("jsBrowserTest")) { this.enabled = false } } 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 c2eedb29..b9c1e2bd 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt @@ -1,19 +1,17 @@ package com.akuleshov7.ktoml import com.akuleshov7.ktoml.decoders.TomlMainDecoder +import com.akuleshov7.ktoml.encoders.TomlMainEncoder import com.akuleshov7.ktoml.exceptions.MissingRequiredPropertyException import com.akuleshov7.ktoml.parsers.TomlParser -import com.akuleshov7.ktoml.tree.TomlFile +import com.akuleshov7.ktoml.tree.nodes.TomlFile import com.akuleshov7.ktoml.utils.findPrimitiveTableInAstByName 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 @@ -21,18 +19,20 @@ import kotlinx.serialization.modules.SerializersModule * Toml class - is a general entry point in the core, * that is used to serialize/deserialize TOML file or string * - * @property config - configuration for the serialization + * @property inputConfig - configuration for deserialization + * @property outputConfig - configuration for serialization * @property serializersModule - default overridden */ @OptIn(ExperimentalSerializationApi::class) public open class Toml( - private val config: TomlConfig = TomlConfig(), - override val serializersModule: SerializersModule = EmptySerializersModule + protected val inputConfig: TomlInputConfig = TomlInputConfig(), + protected val outputConfig: TomlOutputConfig = TomlOutputConfig(), + override val serializersModule: SerializersModule = EmptySerializersModule(), ) : StringFormat { // 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) + public val tomlParser: TomlParser = TomlParser(inputConfig) + public val tomlWriter: TomlWriter = TomlWriter(outputConfig) // ================== basic overrides =============== @@ -44,11 +44,12 @@ public open class Toml( */ override fun decodeFromString(deserializer: DeserializationStrategy, string: String): T { val parsedToml = tomlParser.parseString(string) - return TomlMainDecoder.decode(deserializer, parsedToml, config) + return TomlMainDecoder.decode(deserializer, parsedToml, inputConfig) } override fun encodeToString(serializer: SerializationStrategy, value: T): String { - TODO("Not yet implemented") + val toml = TomlMainEncoder.encode(serializer, value, outputConfig, serializersModule) + return tomlWriter.writeToString(file = toml) } // ================== custom decoding methods =============== @@ -64,7 +65,7 @@ public open class Toml( public fun decodeFromString( deserializer: DeserializationStrategy, toml: List, - config: TomlConfig + config: TomlInputConfig = this.inputConfig ): T = decodeFromString(deserializer, toml.asSequence(), config) /** @@ -78,34 +79,10 @@ public open class Toml( public fun decodeFromString( deserializer: DeserializationStrategy, toml: Sequence, - config: TomlConfig = this.config + config: TomlInputConfig = this.inputConfig ): T { val parsedToml = tomlParser.parseStringsToTomlTree(toml, config) - return TomlMainDecoder.decode(deserializer, parsedToml, this.config) - } - - /** - * partial deserializer of a string in a toml format (separated by newlines). - * Will deserialize only the part presented under the tomlTableName table. - * If such table is missing in he input - will throw an exception - * - * (!) Useful when you would like to deserialize only ONE table - * and you do not want to reproduce whole object structure in the code - * - * @param deserializer deserialization strategy - * @param toml request-string in toml format with '\n' or '\r\n' separation - * @param tomlTableName fully qualified name of the toml table (it should be the full name - a.b.c.d) - * @param config - * @return deserialized object of type T - */ - public fun partiallyDecodeFromString( - deserializer: DeserializationStrategy, - toml: String, - tomlTableName: String, - config: TomlConfig = TomlConfig() - ): T { - val fakeFileNode = generateFakeTomlStructureForPartialParsing(toml, tomlTableName, config, TomlParser::parseString) - return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config) + return TomlMainDecoder.decode(deserializer, parsedToml, this.inputConfig) } /** @@ -126,10 +103,10 @@ public open class Toml( deserializer: DeserializationStrategy, tomlLines: Sequence, tomlTableName: String, - config: TomlConfig = TomlConfig() + config: TomlInputConfig = this.inputConfig ): T { val fakeFileNode = generateFakeTomlStructureForPartialParsing(tomlLines, tomlTableName, config, TomlParser::parseLines) - return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config) + return TomlMainDecoder.decode(deserializer, fakeFileNode, this.inputConfig) } /** @@ -141,24 +118,19 @@ public open class Toml( * and you do not want to reproduce whole object structure in the code * * @param deserializer deserialization strategy - * @param toml list of strings with toml input + * @param toml request-string in toml format with '\n' or '\r\n' separation * @param tomlTableName fully qualified name of the toml table (it should be the full name - a.b.c.d) * @param config * @return deserialized object of type T */ public fun partiallyDecodeFromString( deserializer: DeserializationStrategy, - toml: List, + toml: String, tomlTableName: String, - config: TomlConfig = TomlConfig() + config: TomlInputConfig = this.inputConfig ): T { - val fakeFileNode = generateFakeTomlStructureForPartialParsing( - toml.joinToString("\n"), - tomlTableName, - config, - TomlParser::parseString, - ) - return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config) + val fakeFileNode = generateFakeTomlStructureForPartialParsing(toml, tomlTableName, config, TomlParser::parseString) + return TomlMainDecoder.decode(deserializer, fakeFileNode, config) } /** @@ -179,7 +151,7 @@ public open class Toml( deserializer: DeserializationStrategy, tomlLines: Sequence, tomlTableName: String, - config: TomlConfig = TomlConfig() + config: TomlInputConfig = this.inputConfig ): T { val fakeFileNode = generateFakeTomlStructureForPartialParsing( tomlLines, @@ -187,7 +159,7 @@ public open class Toml( config, TomlParser::parseLines, ) - return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config) + return TomlMainDecoder.decode(deserializer, fakeFileNode, this.inputConfig) } // ================== other =============== @@ -195,10 +167,10 @@ public open class Toml( private fun generateFakeTomlStructureForPartialParsing( tomlInput: I, tomlTableName: String, - config: TomlConfig = TomlConfig(), + config: TomlInputConfig = TomlInputConfig(), parsingFunction: (TomlParser, I) -> TomlFile ): TomlFile { - val tomlFile = parsingFunction(TomlParser(this.config), tomlInput) + val tomlFile = parsingFunction(TomlParser(config), tomlInput) val parsedToml = findPrimitiveTableInAstByName(listOf(tomlFile), tomlTableName) ?: throw MissingRequiredPropertyException( "Cannot find table with name <$tomlTableName> in the toml input. " + @@ -207,7 +179,7 @@ public open class Toml( ) // adding a fake file node to restore the structure and parse only the part of te toml - val fakeFileNode = TomlFile(config) + val fakeFileNode = TomlFile() parsedToml.children.forEach { fakeFileNode.appendChild(it) } @@ -221,5 +193,8 @@ public open class Toml( * ThreadLocal annotation is used here for caching. */ @ThreadLocal - public companion object Default : Toml(TomlConfig()) + public companion object Default : Toml( + inputConfig = TomlInputConfig(), + outputConfig = TomlOutputConfig() + ) } diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/TomlConfig.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/TomlConfig.kt index e425c8b6..20e6a979 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/TomlConfig.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/TomlConfig.kt @@ -24,6 +24,9 @@ public class KtomlConf( * @property indentation - the number of spaces in the indents for the serialization * @property allowEmptyToml - controls if empty toml can be processed, if false - will throw an exception */ +@Deprecated( + message = "Class split into TomlInputConfig and TomlOutputConfig. Will be removed in next releases." +) public open class TomlConfig( public val ignoreUnknownNames: Boolean = false, public val allowEmptyValues: Boolean = true, @@ -32,14 +35,123 @@ public open class TomlConfig( public val indentation: Indentation = Indentation.FOUR_SPACES, public val allowEmptyToml: Boolean = true, ) { + internal val input = TomlInputConfig( + ignoreUnknownNames, + allowEmptyValues, + allowNullValues, + allowEmptyToml, + allowEscapedQuotesInLiteralStrings + ) + internal val output = TomlOutputConfig( + indentation.toTomlIndentation(), + allowEscapedQuotesInLiteralStrings + ) + /** * @property value - string with indents, used for the formatting of serialization */ + @Deprecated( + message = "Enum moved to top-level.", + replaceWith = ReplaceWith( + "TomlIndentation", + "com.akuleshov7.ktoml.TomlIndentation" + ) + ) public enum class Indentation(public val value: String) { FOUR_SPACES(" "), NONE(""), TAB("\t"), TWO_SPACES(" "), ; + + internal fun toTomlIndentation() = TomlIndentation.valueOf(name) + } +} + +/** + * A config to change parsing behavior. + * @property ignoreUnknownNames Whether to allow/prohibit unknown names during the deserialization + * @property allowEmptyValues Whether to allow/prohibit empty values: a = # comment + * @property allowNullValues Whether to allow/prohibit null values: a = null + * @property allowEmptyToml Whether empty toml can be processed, if false - will throw an exception + * @property allowEscapedQuotesInLiteralStrings Whether to allow/prohibit escaping of single quotes in literal strings + */ +public data class TomlInputConfig( + public val ignoreUnknownNames: Boolean = false, + public val allowEmptyValues: Boolean = true, + public val allowNullValues: Boolean = true, + public val allowEmptyToml: Boolean = true, + public val allowEscapedQuotesInLiteralStrings: Boolean = true, +) { + public companion object { + /** + * Creates a config populated with values compliant with the TOML spec. + * + * @param ignoreUnknownNames Whether to allow/prohibit unknown names during the deserialization + * @param allowEmptyToml Whether empty toml can be processed, if false - will throw an exception + * @return A TOML spec-compliant input config + */ + public fun compliant( + ignoreUnknownNames: Boolean = false, + allowEmptyToml: Boolean = true + ): TomlInputConfig = + TomlInputConfig( + ignoreUnknownNames, + allowEmptyValues = false, + allowNullValues = false, + allowEmptyToml, + allowEscapedQuotesInLiteralStrings = false + ) } } + +/** + * A config to change writing behavior. + * + * @property indentation The number of spaces in the indents for the serialization + * @property allowEscapedQuotesInLiteralStrings Whether to allow/prohibit escaping of single quotes in literal strings + * @property ignoreNullValues Whether to ignore null values + * @property ignoreDefaultValues Whether to ignore default values + * @property explicitTables Whether to explicitly define parent tables + */ +public data class TomlOutputConfig( + public val indentation: TomlIndentation = TomlIndentation.FOUR_SPACES, + public val allowEscapedQuotesInLiteralStrings: Boolean = true, + public val ignoreNullValues: Boolean = true, + public val ignoreDefaultValues: Boolean = false, + public val explicitTables: Boolean = false, +) { + public companion object { + /** + * Creates a config populated with values compliant with the TOML spec. + * + * @param indentation The number of spaces in the indents for the serialization + * @param ignoreDefaultValues Whether to ignore default values + * @param explicitTables Whether to explicitly define parent tables + * @return A TOML spec-compliant output config + */ + public fun compliant( + indentation: TomlIndentation = TomlIndentation.FOUR_SPACES, + ignoreDefaultValues: Boolean = false, + explicitTables: Boolean = false, + ): TomlOutputConfig = + TomlOutputConfig( + indentation, + allowEscapedQuotesInLiteralStrings = false, + ignoreNullValues = true, + ignoreDefaultValues, + explicitTables + ) + } +} + +/** + * @property value The indent string, used for the formatting during serialization + */ +public enum class TomlIndentation(public val value: String) { + FOUR_SPACES(" "), + NONE(""), + TAB("\t"), + TWO_SPACES(" "), + ; +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlComments.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlComments.kt index f3e7e1db..0d4e50fb 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlComments.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlComments.kt @@ -4,6 +4,9 @@ import kotlin.annotation.AnnotationTarget.PROPERTY import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialInfo +// FixMe: Default parameters after varargs are broken as of 1.7: KT-53235. There's +// no obvious workaround + /** * Specifies comments to be applied the TOML element produced by a property during * serialization. Has no effect on deserialization. diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlInlineTable.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlInlineTable.kt index 894ce757..e68054c2 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlInlineTable.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlInlineTable.kt @@ -46,6 +46,7 @@ import kotlinx.serialization.SerialInfo @Target( PROPERTY, TYPE_PARAMETER, - CLASS + CLASS, + TYPE ) public annotation class TomlInlineTable diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlInteger.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlInteger.kt index 380891c6..72d43df5 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlInteger.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlInteger.kt @@ -2,8 +2,8 @@ package com.akuleshov7.ktoml.annotations import com.akuleshov7.ktoml.writers.IntegerRepresentation import com.akuleshov7.ktoml.writers.IntegerRepresentation.DECIMAL -import kotlin.annotation.AnnotationTarget.PROPERTY -import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER + +import kotlin.annotation.AnnotationTarget.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialInfo @@ -36,7 +36,11 @@ import kotlinx.serialization.SerialInfo */ @OptIn(ExperimentalSerializationApi::class) @SerialInfo -@Target(PROPERTY, TYPE_PARAMETER) +@Target( + PROPERTY, + TYPE_PARAMETER, + TYPE +) public annotation class TomlInteger( val representation: IntegerRepresentation = DECIMAL ) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlLiteral.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlLiteral.kt index 5c9554a3..dbd8316f 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlLiteral.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlLiteral.kt @@ -1,7 +1,6 @@ package com.akuleshov7.ktoml.annotations -import kotlin.annotation.AnnotationTarget.PROPERTY -import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER +import kotlin.annotation.AnnotationTarget.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialInfo @@ -31,5 +30,9 @@ import kotlinx.serialization.SerialInfo */ @OptIn(ExperimentalSerializationApi::class) @SerialInfo -@Target(PROPERTY, TYPE_PARAMETER) +@Target( + PROPERTY, + TYPE_PARAMETER, + TYPE +) public annotation class TomlLiteral diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlMultiline.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlMultiline.kt index bf6834c2..2c21e3e6 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlMultiline.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/annotations/TomlMultiline.kt @@ -1,7 +1,6 @@ package com.akuleshov7.ktoml.annotations -import kotlin.annotation.AnnotationTarget.PROPERTY -import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER +import kotlin.annotation.AnnotationTarget.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialInfo @@ -58,5 +57,9 @@ import kotlinx.serialization.SerialInfo */ @OptIn(ExperimentalSerializationApi::class) @SerialInfo -@Target(PROPERTY, TYPE_PARAMETER) +@Target( + PROPERTY, + TYPE_PARAMETER, + TYPE +) public annotation class TomlMultiline 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 12a638f2..8c64bc1c 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 @@ -1,11 +1,20 @@ package com.akuleshov7.ktoml.decoders -import com.akuleshov7.ktoml.exceptions.CastException import com.akuleshov7.ktoml.exceptions.IllegalTypeException -import com.akuleshov7.ktoml.tree.TomlKeyValue +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValue +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlBasicString +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlDouble +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlLiteralString +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlLong +import com.akuleshov7.ktoml.utils.FloatingPointLimitsEnum +import com.akuleshov7.ktoml.utils.FloatingPointLimitsEnum.* +import com.akuleshov7.ktoml.utils.IntegerLimitsEnum +import com.akuleshov7.ktoml.utils.IntegerLimitsEnum.* +import com.akuleshov7.ktoml.utils.convertSpecialCharacters import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.encoding.AbstractDecoder @@ -19,13 +28,50 @@ public abstract class TomlAbstractDecoder : AbstractDecoder() { private val instantSerializer = Instant.serializer() private val localDateTimeSerializer = LocalDateTime.serializer() private val localDateSerializer = LocalDate.serializer() + private val localTimeSerializer = LocalTime.serializer() - // Invalid Toml primitive types, we will simply throw an error for them - override fun decodeByte(): Byte = invalidType("Byte", "Long") - override fun decodeShort(): Short = invalidType("Short", "Long") - override fun decodeInt(): Int = invalidType("Int", "Long") - override fun decodeFloat(): Float = invalidType("Float", "Double") - override fun decodeChar(): Char = invalidType("Char", "String") + // Invalid Toml primitive types, but we anyway support them with some limitations + override fun decodeByte(): Byte = decodePrimitiveType() + override fun decodeShort(): Short = decodePrimitiveType() + override fun decodeInt(): Int = decodePrimitiveType() + override fun decodeFloat(): Float = decodePrimitiveType() + override fun decodeChar(): Char { + val keyValue = decodeKeyValue() + return when (val value = keyValue.value) { + // converting to Char from a parsed Long number and checking bounds for the Char (MIN-MAX range) + is TomlLong -> validateAndConvertInteger(value.content as Long, keyValue.lineNo, CHAR) { Char(it.toInt()) } + // converting to Char from a parsed Literal String (with single quotes: '') + is TomlLiteralString -> + try { + (value.content as String).convertSpecialCharacters(keyValue.lineNo).single() + } catch (ex: NoSuchElementException) { + throw IllegalTypeException( + "Empty value is not allowed for type [Char], " + + "please check the value: [${value.content}] or use [String] type for deserialization of " + + "[${keyValue.key}] instead", keyValue.lineNo + ) + } catch (ex: IllegalArgumentException) { + throw IllegalTypeException( + "[Char] type should be used for decoding of single character, but " + + "received multiple characters instead: [${value.content}]. " + + "If you really want to decode multiple chars, use [String] instead.", keyValue.lineNo + ) + } + // to avoid confusion, we prohibit basic strings with double quotes for decoding to a Char type + is TomlBasicString -> throw IllegalTypeException( + "Double quotes were used in the input for deserialization " + + "of [Char]. Use [String] type or single quotes ('') instead for: [${value.content}]", + keyValue.lineNo + ) + // all other toml tree types are not supported + else -> throw IllegalTypeException( + "Cannot decode the key [${keyValue.key.last()}] with the value [${keyValue.value.content}]" + + " and with the provided type [Char]. Please check the type" + + " in your Serializable class or it's nullability", + keyValue.lineNo + ) + } + } // Valid Toml types that should be properly decoded override fun decodeBoolean(): Boolean = decodePrimitiveType() @@ -36,38 +82,126 @@ public abstract class TomlAbstractDecoder : AbstractDecoder() { protected fun DeserializationStrategy<*>.isDateTime(): Boolean = descriptor == instantSerializer.descriptor || descriptor == localDateTimeSerializer.descriptor || - descriptor == localDateSerializer.descriptor + descriptor == localDateSerializer.descriptor || + descriptor == localTimeSerializer.descriptor // Cases for date-time types @Suppress("UNCHECKED_CAST") - override fun decodeSerializableValue(deserializer: DeserializationStrategy): T = when (deserializer.descriptor) { - instantSerializer.descriptor -> decodePrimitiveType() as T - localDateTimeSerializer.descriptor -> decodePrimitiveType() as T - localDateSerializer.descriptor -> decodePrimitiveType() as T - else -> super.decodeSerializableValue(deserializer) - } + override fun decodeSerializableValue(deserializer: DeserializationStrategy): T = + when (deserializer.descriptor) { + instantSerializer.descriptor -> decodePrimitiveType() as T + localDateTimeSerializer.descriptor -> decodePrimitiveType() as T + localDateSerializer.descriptor -> decodePrimitiveType() as T + localTimeSerializer.descriptor -> decodePrimitiveType() as T + else -> super.decodeSerializableValue(deserializer) + } internal abstract fun decodeKeyValue(): TomlKeyValue - private fun invalidType(typeName: String, requiredType: String): Nothing { - val keyValue = decodeKeyValue() - throw IllegalTypeException( - "<$typeName> type is not allowed by toml specification," + - " use <$requiredType> instead" + - " (key = ${keyValue.key.content}; value = ${keyValue.value.content})", keyValue.lineNo - ) - } - + /** + * This is just an adapter from `kotlinx.serialization` to match the content with a type from a Toml Tree, + * that we have parsed to a type that is described in user's code. For example: + * >>> input: a = "5" + * >>> stored in Toml Tree: TomlString("5") + * >>> expected by user: data class A(val a: Int) + * >>> TomlString cannot be cast to Int, user made a mistake -> IllegalTypeException + */ private inline fun decodePrimitiveType(): T { val keyValue = decodeKeyValue() try { - return keyValue.value.content as T + return when (val value = keyValue.value) { + is TomlLong -> decodeInteger(value.content as Long, keyValue.lineNo) + is TomlDouble -> decodeFloatingPoint(value.content as Double, keyValue.lineNo) + else -> keyValue.value.content as T + } } catch (e: ClassCastException) { - throw CastException( - "Cannot decode the key [${keyValue.key.content}] with the value [${keyValue.value.content}]" + - " with the provided type [${T::class}]. Please check the type in your Serializable class or it's nullability", + throw IllegalTypeException( + "Cannot decode the key [${keyValue.key.last()}] with the value [${keyValue.value.content}]" + + " and with the provided type [${T::class}]. Please check the type in your Serializable class or it's nullability", keyValue.lineNo ) } } + + private inline fun decodeFloatingPoint(content: Double, lineNo: Int): T = + when (T::class) { + Float::class -> validateAndConvertFloatingPoint( + content, + lineNo, + FLOAT + ) { num: Double -> num.toFloat() as T } + + Double::class -> validateAndConvertFloatingPoint(content, lineNo, DOUBLE) { num: Double -> num as T } + else -> invalidType(T::class.toString(), "Signed Type") + } + + /** + * ktoml parser treats all integer literals as Long and all floating-point literals as Double, + * so here we should be checking that there is no overflow with smaller types like Byte, Short and Int. + */ + private inline fun validateAndConvertFloatingPoint( + content: Double, + lineNo: Int, + limits: FloatingPointLimitsEnum, + conversion: (Double) -> T, + ): T = when { + content.isInfinite() || content.isNaN() -> conversion(content) + content in limits.min..limits.max -> conversion(content) + else -> throw IllegalTypeException( + "The floating point literal, that you have provided is <$content>, " + + "but the type for deserialization is <${T::class}>. You will get an overflow, " + + "so we advise you to check the data or use other type for deserialization (Long, for example)", + lineNo + ) + } + + /** + * After a lot of discussions (https://github.com/akuleshov7/ktoml/pull/153#discussion_r1003114861 and + * https://github.com/akuleshov7/ktoml/issues/163), we have finally decided to allow to use Integer types and not only Long. + * This method does simple validation of integer values to avoid overflow. For example, you really want to use byte, + * we will check here, that your byte value does not exceed 127 and so on. + */ + private inline fun decodeInteger(content: Long, lineNo: Int): T = + when (T::class) { + Byte::class -> validateAndConvertInteger(content, lineNo, BYTE) { it.toByte() as T } + Short::class -> validateAndConvertInteger(content, lineNo, SHORT) { it.toShort() as T } + Int::class -> validateAndConvertInteger(content, lineNo, INT) { it.toInt() as T } + Long::class -> validateAndConvertInteger(content, lineNo, LONG) { it as T } + Double::class, Float::class -> throw IllegalTypeException( + "Expected floating-point number, but received integer literal: <$content>. " + + "Deserialized floating-point number should have a dot: <$content.0>", + lineNo + ) + + else -> invalidType(T::class.toString(), "Signed Type") + } + + /** + * ktoml parser treats all integer literals as Long and all floating-point literals as Double, + * so here we should be checking that there is no overflow with smaller types like Byte, Short and Int. + */ + private inline fun validateAndConvertInteger( + content: Long, + lineNo: Int, + limits: IntegerLimitsEnum, + conversion: (Long) -> T, + ): T = if (content in limits.min..limits.max) { + conversion(content) + } else { + throw IllegalTypeException( + "The integer literal, that you have provided is <$content>, " + + "but the type for deserialization is <${T::class}>. You will get an overflow, " + + "so we advise you to check the data or use other type for deserialization (Long, for example)", + lineNo + ) + } + + private fun invalidType(typeName: String, requiredType: String): Nothing { + val keyValue = decodeKeyValue() + throw IllegalTypeException( + "<$typeName> type is not allowed by toml specification," + + " use <$requiredType> instead" + + " (key = ${keyValue.key.last()}; value = ${keyValue.value.content})", keyValue.lineNo + ) + } } 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 951e4e05..f487f564 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,12 @@ package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.TomlConfig -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 com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValue +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValueArray +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlNull +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlValue import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.descriptors.SerialDescriptor @@ -21,14 +22,22 @@ import kotlinx.serialization.modules.SerializersModule @Suppress("UNCHECKED_CAST") public class TomlArrayDecoder( private val rootNode: TomlKeyValueArray, - private val config: TomlConfig, + private val config: TomlInputConfig, ) : TomlAbstractDecoder() { private var nextElementIndex = 0 private val list = rootNode.value.content as List - override val serializersModule: SerializersModule = EmptySerializersModule + override val serializersModule: SerializersModule = EmptySerializersModule() private lateinit var currentElementDecoder: TomlPrimitiveDecoder private lateinit var currentPrimitiveElementOfArray: TomlValue + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + rootNode: TomlKeyValueArray, + config: TomlConfig + ) : this(rootNode, config.input) + private fun haveStartedReadingElements() = nextElementIndex > 0 override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = list.size @@ -46,8 +55,8 @@ public class TomlArrayDecoder( rootNode.key, currentPrimitiveElementOfArray, rootNode.lineNo, - rootNode.key.content, - config + comments = emptyList(), + inlineComment = "", ) ) return nextElementIndex++ 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 13d96b5e..137d450f 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 @@ -3,10 +3,12 @@ package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig import com.akuleshov7.ktoml.exceptions.* -import com.akuleshov7.ktoml.tree.* -import com.akuleshov7.ktoml.tree.TomlNull -import com.akuleshov7.ktoml.tree.TomlTablePrimitive +import com.akuleshov7.ktoml.tree.nodes.* +import com.akuleshov7.ktoml.tree.nodes.TomlFile +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlNull + import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.descriptors.SerialDescriptor @@ -26,10 +28,23 @@ import kotlinx.serialization.modules.SerializersModule @ExperimentalSerializationApi public class TomlMainDecoder( private var rootNode: TomlNode, - private val config: TomlConfig, + private val config: TomlInputConfig, private var elementIndex: Int = 0 ) : TomlAbstractDecoder() { - override val serializersModule: SerializersModule = EmptySerializersModule + override val serializersModule: SerializersModule = EmptySerializersModule() + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + rootNode: TomlNode, + config: TomlConfig, + elementIndex: Int = 0 + ) : this( + rootNode, + config.input, + elementIndex + ) override fun decodeValue(): Any = decodeKeyValue().value.content @@ -66,7 +81,7 @@ public class TomlMainDecoder( private fun getCurrentNode() = rootNode.getNeighbourNodes().elementAt(elementIndex - 1) /** - * Trying to decode the value (ite + * Trying to decode the value using elementIndex * |--- child1, child2, ... , childN * ------------elementIndex-------> * @@ -222,14 +237,14 @@ public class TomlMainDecoder( is TomlTablePrimitive -> { val firstTableChild = nextProcessingNode.getFirstChild() ?: throw InternalDecodingException( "Decoding process failed due to invalid structure of parsed AST tree: missing children" + - " in a table <${nextProcessingNode.fullTableName}>" + " in a table <${nextProcessingNode.fullTableKey}>" ) checkMissingRequiredProperties(firstTableChild.getNeighbourNodes(), descriptor) TomlMainDecoder(firstTableChild, config) } else -> throw InternalDecodingException( "Incorrect decoding state in the beginStructure()" + - " with $nextProcessingNode (${nextProcessingNode.content})[${nextProcessingNode.name}]" + " with $nextProcessingNode ($nextProcessingNode)[${nextProcessingNode.name}]" ) } } @@ -254,7 +269,7 @@ public class TomlMainDecoder( public fun decode( deserializer: DeserializationStrategy, rootNode: TomlNode, - config: TomlConfig = TomlConfig() + config: TomlInputConfig = TomlInputConfig() ): T { val decoder = TomlMainDecoder(rootNode, config) return decoder.decodeSerializableValue(deserializer) 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 b9a35aa4..3c26e148 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.tree.TomlKeyValue -import com.akuleshov7.ktoml.tree.TomlKeyValuePrimitive +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValue +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.modules.EmptySerializersModule @@ -14,7 +14,7 @@ import kotlinx.serialization.modules.SerializersModule public class TomlPrimitiveDecoder( private val rootNode: TomlKeyValuePrimitive, ) : TomlAbstractDecoder() { - override val serializersModule: SerializersModule = EmptySerializersModule + override val serializersModule: SerializersModule = EmptySerializersModule() override fun decodeValue(): Any = rootNode.value.content override fun decodeElementIndex(descriptor: SerialDescriptor): Int = 0 override fun decodeKeyValue(): TomlKeyValue = rootNode diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt new file mode 100644 index 00000000..720ad13e --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt @@ -0,0 +1,295 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.exceptions.InternalEncodingException +import com.akuleshov7.ktoml.exceptions.UnsupportedEncodingFeatureException +import com.akuleshov7.ktoml.tree.nodes.TomlNode +import com.akuleshov7.ktoml.tree.nodes.pairs.values.* +import com.akuleshov7.ktoml.utils.isBareKey +import com.akuleshov7.ktoml.utils.isLiteralKeyCandidate +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.AbstractEncoder +import kotlinx.serialization.modules.SerializersModule + +/** + * An abstract Encoder for the TOML format. + * + * @property elementIndex The current element index. + * @property attributes The current attributes. + * @property outputConfig The output config. + * @property serializersModule + */ +@OptIn(ExperimentalSerializationApi::class) +public abstract class TomlAbstractEncoder protected constructor( + protected var elementIndex: Int, + protected val attributes: TomlEncoderAttributes, + protected val outputConfig: TomlOutputConfig, + override val serializersModule: SerializersModule, +) : AbstractEncoder() { + private var isNextElementKey = false + private val instantDescriptor = Instant.serializer().descriptor + private val localDateTimeDescriptor = LocalDateTime.serializer().descriptor + private val localDateDescriptor = LocalDate.serializer().descriptor + private val localTimeDescriptor = LocalTime.serializer().descriptor + + protected open fun nextElementIndex(): Int = ++elementIndex + + // Values + + @Suppress("FUNCTION_BOOLEAN_PREFIX") + private fun encodeAsKey(key: Any, type: String? = null): Boolean { + if (!isNextElementKey) { + return false + } + + isNextElementKey = false + + if (key !is String) { + throw UnsupportedEncodingFeatureException( + "Arbitrary map key types are not supported. Must be either a string" + + " or enum. Provide a custom serializer for $type to either " + + "of the supported key types." + ) + } + + setKey(key) + + return true + } + + protected open fun appendValue(value: TomlValue) { + attributes.reset() + } + + /** + * Allows [TomlInlineTableEncoder] and [TomlArrayEncoder] to access another + * encoder's [appendValue] function. + * + * @param value + * @param parent + */ + internal fun appendValueTo(value: TomlValue, parent: TomlAbstractEncoder) { + parent.appendValue(value) + } + + override fun encodeBoolean(value: Boolean) { + if (!encodeAsKey(value, "Boolean")) { + appendValue(TomlBoolean(value)) + } + } + + override fun encodeDouble(value: Double) { + if (!encodeAsKey(value, "Double")) { + appendValue(TomlDouble(value)) + } + } + + override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { + encodeString(enumDescriptor.getElementName(index)) + } + + override fun encodeLong(value: Long) { + if (!encodeAsKey(value, "Long")) { + appendValue(TomlLong(value, attributes.intRepresentation)) + } + } + + override fun encodeNull() { + appendValue(TomlNull()) + } + + override fun encodeString(value: String) { + if (!encodeAsKey(value)) { + appendValue( + if (attributes.isLiteral) { + TomlLiteralString(value, attributes.isMultiline) + } else { + TomlBasicString(value, attributes.isMultiline) + } + ) + } + } + + override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { + when (val desc = serializer.descriptor) { + instantDescriptor, + localDateTimeDescriptor, + localDateDescriptor, + localTimeDescriptor -> if (!encodeAsKey(value as Any, desc.serialName)) { + appendValue(TomlDateTime(value)) + } + else -> when (val kind = desc.kind) { + is StructureKind, + is PolymorphicKind -> if (!encodeAsKey(value as Any, desc.serialName)) { + val encoder = encodeStructure(kind) + + serializer.serialize(encoder, value) + + elementIndex = encoder.elementIndex + + attributes.reset() + } + else -> super.encodeSerializableValue(serializer, value) + } + } + } + + override fun encodeByte(value: Byte): Unit = encodeLong(value.toLong()) + override fun encodeShort(value: Short): Unit = encodeLong(value.toLong()) + override fun encodeInt(value: Int): Unit = encodeLong(value.toLong()) + override fun encodeFloat(value: Float): Unit = encodeDouble(value.toDouble()) + override fun encodeChar(value: Char): Unit = encodeString(value.toString()) + + // Structure + + protected abstract fun encodeStructure(kind: SerialKind): TomlAbstractEncoder + + override fun shouldEncodeElementDefault( + descriptor: SerialDescriptor, + index: Int + ): Boolean = !outputConfig.ignoreDefaultValues + + override fun encodeNullableSerializableElement( + descriptor: SerialDescriptor, + index: Int, + serializer: SerializationStrategy, + value: T? + ) { + if (value != null || !outputConfig.ignoreNullValues) { + super.encodeNullableSerializableElement(descriptor, index, serializer, value) + } + } + + override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean { + if (isNextElementKey(descriptor, index)) { + return true + } + + nextElementIndex() + + val typeDescriptor = descriptor.getElementDescriptor(index) + val typeAnnotations = typeDescriptor.annotations + val elementAnnotations = descriptor.getElementAnnotations(index) + + attributes.set(typeAnnotations) + attributes.set(elementAnnotations) + + // Force primitive array elements to be inline. + if (!attributes.isInline && typeDescriptor.kind == StructureKind.LIST) { + when (typeDescriptor.getElementDescriptor(0).kind) { + is PrimitiveKind, + SerialKind.ENUM, + StructureKind.LIST -> attributes.isInline = true + else -> { } + } + } + + // Force primitive array elements to be single-line. + if (attributes.isInline && descriptor.kind == StructureKind.LIST) { + when (typeDescriptor.kind) { + is PrimitiveKind, + SerialKind.ENUM -> attributes.isMultiline = false + else -> { } + } + } + + return true + } + + protected open fun isNextElementKey(descriptor: SerialDescriptor, index: Int): Boolean { + when (val kind = descriptor.kind) { + StructureKind.CLASS -> setKey(descriptor.getElementName(index)) + StructureKind.MAP -> { + // When the index is even (key) mark the next element as a key and + // skip annotations and element index incrementing. + if (index % 2 == 0) { + isNextElementKey = true + + return true + } + } + is PolymorphicKind -> { + setKey(descriptor.getElementName(index)) + + // Ignore annotations on polymorphic types. + if (index == 0) { + nextElementIndex() + return true + } + } + else -> throw InternalEncodingException("Unknown parent kind: $kind.") + } + + return false + } + + /** + * Set the key attribute to [key], quoting and escaping as necessary. + * + * @param key + */ + protected fun setKey(key: String) { + attributes.key = when { + key.isBareKey() -> key + key.isLiteralKeyCandidate() -> "'$key'" + else -> "\"$key\"" + } + } + + /** + * Creates a new array encoder instance, with this encoder as its parent. + * + * @param rootNode The new encoder's root node. + * @param attributes The new encoder's attributes. + * @return The new instance. + */ + protected open fun arrayEncoder( + rootNode: TomlNode, + attributes: TomlEncoderAttributes = this.attributes.child() + ): TomlAbstractEncoder = + TomlArrayEncoder( + rootNode, + parent = this, + elementIndex, + attributes, + outputConfig, + serializersModule + ) + + /** + * Creates a new inline table encoder instance, with the encoder as its parent. + * + * @param rootNode The new encoder's root node. + * @return The new instance. + */ + protected open fun inlineTableEncoder(rootNode: TomlNode): TomlAbstractEncoder = + TomlInlineTableEncoder( + rootNode, + parent = this, + elementIndex, + attributes.child(), + outputConfig, + serializersModule + ) + + /** + * Creates a new table encoder instance. + * + * @param rootNode The new encoder's root node. + * @return The new instance. + */ + protected open fun tableEncoder(rootNode: TomlNode): TomlAbstractEncoder = + TomlMainEncoder( + rootNode, + elementIndex, + attributes.child(), + outputConfig, + serializersModule + ) +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlArrayEncoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlArrayEncoder.kt new file mode 100644 index 00000000..62a84cd3 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlArrayEncoder.kt @@ -0,0 +1,173 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.exceptions.InternalEncodingException +import com.akuleshov7.ktoml.exceptions.UnsupportedEncodingFeatureException +import com.akuleshov7.ktoml.tree.nodes.TomlArrayOfTables +import com.akuleshov7.ktoml.tree.nodes.TomlArrayOfTablesElement +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValueArray +import com.akuleshov7.ktoml.tree.nodes.TomlNode +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlArray +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlValue +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.descriptors.PolymorphicKind +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.SerialKind +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.modules.SerializersModule + +/** + * Encodes a TOML array or table array. + */ +@OptIn(ExperimentalSerializationApi::class) +public class TomlArrayEncoder internal constructor( + private val rootNode: TomlNode, + private val parent: TomlAbstractEncoder?, + elementIndex: Int, + attributes: TomlEncoderAttributes, + outputConfig: TomlOutputConfig, + serializersModule: SerializersModule +) : TomlAbstractEncoder( + elementIndex, + attributes, + outputConfig, + serializersModule +) { + private val values: MutableList = mutableListOf() + private val tables: MutableList = mutableListOf() + + /** + * @param rootNode The root node to add the array to. + * @param elementIndex The current element index. + * @param attributes The current attributes. + * @param inputConfig The input config, used for constructing nodes. + * @param outputConfig The output config. + */ + public constructor( + rootNode: TomlNode, + elementIndex: Int, + attributes: TomlEncoderAttributes, + outputConfig: TomlOutputConfig, + serializersModule: SerializersModule + ) : this( + rootNode, + parent = null, + elementIndex, + attributes, + outputConfig, + serializersModule + ) + + override fun nextElementIndex(): Int { + // All key-value array elements are on the same line; only increment for + // table arrays. + return if (!attributes.isInline) { + super.nextElementIndex() + } else { + elementIndex + } + } + + override fun isNextElementKey(descriptor: SerialDescriptor, index: Int): Boolean = false + + override fun appendValue(value: TomlValue) { + values += value + + // If a primitive array somehow wasn't already marked as such, do so. + if (!attributes.parent!!.isInline) { + if (tables.isNotEmpty()) { + throw InternalEncodingException("Primitive value added to table array") + } + + attributes.parent.isInline = true + } + + super.appendValue(value) + } + + override fun encodeStructure(kind: SerialKind): TomlAbstractEncoder = if (attributes.isInline) { + when (kind) { + StructureKind.LIST, + is PolymorphicKind -> + // Nested primitive array + arrayEncoder(rootNode, attributes) + else -> + throw UnsupportedEncodingFeatureException( + "Inline tables are not yet supported as array elements." + ) + } + } else { + val element = TomlArrayOfTablesElement( + elementIndex, + attributes.comments, + attributes.inlineComment + ) + + tables += element + + TomlMainEncoder( + element, + nextElementIndex(), + attributes, + outputConfig, + serializersModule + ) + } + + override fun endStructure(descriptor: SerialDescriptor) { + if (attributes.isInline) { + val array = TomlArray(values, attributes.isMultiline) + + parent?.let { + appendValueTo(array, parent) + } ?: parent.run { + val key = attributes.parent!!.keyOrThrow() + + // Create a key-array pair and add it to the parent. + rootNode.appendChild( + TomlKeyValueArray( + TomlKey(key, elementIndex), + array, + elementIndex, + attributes.comments, + attributes.inlineComment + ) + ) + } + } else { + // If the root table array contains a single nested table array, move it + // from its element to the root and mark the root as synthetic. + collapseSingleChildRoot() + } + + super.endStructure(descriptor) + } + + private fun collapseSingleChildRoot() { + var isSynthetic = false + + tables.singleOrNull()?.let { element -> + if (element is TomlArrayOfTablesElement) { + element.children.singleOrNull()?.let { nested -> + if (nested is TomlArrayOfTables) { + tables.clear() + + tables += nested + isSynthetic = !outputConfig.explicitTables + } + } + } + } + + val tableArray = TomlArrayOfTables( + TomlKey(attributes.parent!!.getFullKey(), elementIndex), + elementIndex, + isSynthetic + ) + + tables.forEach(tableArray::appendChild) + + rootNode.appendChild(tableArray) + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlEncoderAttributes.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlEncoderAttributes.kt new file mode 100644 index 00000000..37f60f98 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlEncoderAttributes.kt @@ -0,0 +1,75 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.annotations.* +import com.akuleshov7.ktoml.exceptions.InternalEncodingException +import com.akuleshov7.ktoml.writers.IntegerRepresentation + +/** + * @property parent The parent to inherit default values from. + * @property key The current element's key. + * @property isMultiline Marks subsequent key-string or array pair elements to + * be written as multiline. + * @property isLiteral Marks subsequent key-string pair elements to be written + * as string literals. + * @property intRepresentation Changes how subsequent key-integer pair elements + * are represented. + * @property isInline Marks subsequent table-like elements as inline. Tables + * will be written as inline tables. + * @property comments Comment lines to be prepended before the next element. + * @property inlineComment A comment to be appended to the end of the next + * element's line. + * @property isImplicit Whether the current property is implicitly defined in + * its child, i.e. the table `[a]` in `[a.b]`. + */ +public data class TomlEncoderAttributes( + public val parent: TomlEncoderAttributes? = null, + public var key: String? = null, + public var isMultiline: Boolean = false, + public var isLiteral: Boolean = false, + public var intRepresentation: IntegerRepresentation = IntegerRepresentation.DECIMAL, + public var isInline: Boolean = false, + public var comments: List = emptyList(), + public var inlineComment: String = "", + public var isImplicit: Boolean = false, +) { + public fun keyOrThrow(): String = key ?: throw InternalEncodingException("Key not set") + + public fun child(): TomlEncoderAttributes = copy(parent = copy(), isImplicit = false) + + public fun set(annotations: Iterable) { + annotations.forEach { annotation -> + when (annotation) { + is TomlLiteral -> isLiteral = true + is TomlMultiline -> isMultiline = true + is TomlInteger -> intRepresentation = annotation.representation + is TomlComments -> { + comments = annotation.lines.asList() + inlineComment = annotation.inline + } + is TomlInlineTable -> isInline = true + } + } + } + + public fun reset() { + key = null + + val parent = parent ?: TomlEncoderAttributes() + + isMultiline = parent.isMultiline + isLiteral = parent.isLiteral + intRepresentation = parent.intRepresentation + isInline = parent.isInline + comments = parent.comments + inlineComment = parent.inlineComment + isImplicit = false + } + + public fun getFullKey(): String { + val elementKey = keyOrThrow() + + return parent?.let { + "${it.getFullKey()}.$elementKey" + } ?: elementKey + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlInlineTableEncoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlInlineTableEncoder.kt new file mode 100644 index 00000000..464ab4fe --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlInlineTableEncoder.kt @@ -0,0 +1,158 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.exceptions.InternalEncodingException +import com.akuleshov7.ktoml.tree.nodes.TomlInlineTable +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValueArray +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive +import com.akuleshov7.ktoml.tree.nodes.TomlNode +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlArray +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlValue +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.SerialKind +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.encoding.CompositeEncoder +import kotlinx.serialization.modules.SerializersModule + +// Todo: Support "flat keys", i.e. a = { b.c = "..." } + +/** + * Encodes a TOML inline table. + */ +@OptIn(ExperimentalSerializationApi::class) +public class TomlInlineTableEncoder internal constructor( + private val rootNode: TomlNode, + private val parent: TomlAbstractEncoder?, + elementIndex: Int, + attributes: TomlEncoderAttributes, + outputConfig: TomlOutputConfig, + serializersModule: SerializersModule +) : TomlAbstractEncoder( + elementIndex, + attributes, + outputConfig, + serializersModule +) { + private val pairs: MutableList = mutableListOf() + + /** + * @param rootNode The root node to add the inline table to. + * @param elementIndex The current element index. + * @param attributes The current attributes. + * @param inputConfig The input config, used for constructing nodes. + * @param outputConfig The output config. + */ + public constructor( + rootNode: TomlNode, + elementIndex: Int, + attributes: TomlEncoderAttributes, + outputConfig: TomlOutputConfig, + serializersModule: SerializersModule + ) : this( + rootNode, + parent = null, + elementIndex, + attributes, + outputConfig, + serializersModule + ) + + // Inline tables are single-line, don't increment. + override fun nextElementIndex(): Int = elementIndex + + override fun appendValue(value: TomlValue) { + val name = attributes.keyOrThrow() + val key = TomlKey(name, elementIndex) + + pairs += if (value is TomlArray) { + TomlKeyValueArray( + key, + value, + elementIndex, + comments = emptyList(), + inlineComment = "", + ) + } else { + TomlKeyValuePrimitive( + key, + value, + elementIndex, + comments = emptyList(), + inlineComment = "" + ) + } + + super.appendValue(value) + } + + override fun encodeStructure(kind: SerialKind): TomlAbstractEncoder = if (kind == StructureKind.LIST) { + arrayEncoder(rootNode) + } else { + inlineTableEncoder(rootNode) + } + + override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { + val (_, _, _, _, _, _, comments, inlineComment) = attributes + val name = attributes.keyOrThrow() + + val inlineTable = TomlInlineTable( + TomlKey(name, elementIndex), + pairs, + elementIndex, + comments, + inlineComment + ) + + when (parent) { + is TomlInlineTableEncoder -> parent.pairs += inlineTable + is TomlArrayEncoder -> { + // Todo: Implement this when inline table arrays are supported. + } + else -> rootNode.appendChild(inlineTable) + } + + return super.beginStructure(descriptor) + } + + override fun endStructure(descriptor: SerialDescriptor) { + if (!outputConfig.explicitTables && parent is TomlInlineTableEncoder) { + pairs.singleOrNull()?.let { pair -> + parent.collapseLast(pair) + } + } + + super.endStructure(descriptor) + } + + private fun collapseLast(child: TomlNode) { + val target = pairs.removeLast() + val name = "${target.name}.${child.name}" + + pairs += when (child) { + is TomlKeyValuePrimitive -> TomlKeyValuePrimitive( + TomlKey(name, child.lineNo), + child.value, + child.lineNo, + child.comments, + child.inlineComment + ) + is TomlKeyValueArray -> TomlKeyValueArray( + TomlKey(name, child.lineNo), + child.value, + child.lineNo, + child.comments, + child.inlineComment + ) + is TomlInlineTable -> TomlInlineTable( + TomlKey(name, elementIndex), + child.tomlKeyValues, + child.lineNo, + child.comments, + child.inlineComment + ) + else -> throw InternalEncodingException("Not a pair") + } + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlMainEncoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlMainEncoder.kt new file mode 100644 index 00000000..c6540703 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlMainEncoder.kt @@ -0,0 +1,149 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.Toml.Default.outputConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.tree.nodes.* +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlArray +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlValue + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.SerialKind +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule + +/** + * Encodes a TOML file or table. + * + * @property rootNode The root node to add elements to. + * + * @param elementIndex The current element index. + * @param attributes The current attributes. + * @param inputConfig The input config, used for constructing nodes. + * @param outputConfig The output config. + */ +@OptIn(ExperimentalSerializationApi::class) +public class TomlMainEncoder( + private val rootNode: TomlNode, + elementIndex: Int = -1, + attributes: TomlEncoderAttributes = TomlEncoderAttributes(), + outputConfig: TomlOutputConfig = TomlOutputConfig(), + serializersModule: SerializersModule = EmptySerializersModule() +) : TomlAbstractEncoder( + elementIndex, + attributes, + outputConfig, + serializersModule +) { + override fun appendValue(value: TomlValue) { + val (_, _, _, _, _, _, comments, inlineComment) = attributes + + val name = attributes.keyOrThrow() + val key = TomlKey(name, elementIndex) + + rootNode.appendChild( + if (value is TomlArray) { + TomlKeyValueArray( + key, + value, + elementIndex, + comments, + inlineComment + ) + } else { + TomlKeyValuePrimitive( + key, + value, + elementIndex, + comments, + inlineComment + ) + } + ) + + super.appendValue(value) + } + + override fun encodeStructure(kind: SerialKind): TomlAbstractEncoder = when { + kind == StructureKind.LIST -> TomlArrayEncoder( + rootNode, + elementIndex, + attributes.child(), + outputConfig, + serializersModule + ) + attributes.isInline -> TomlInlineTableEncoder( + rootNode, + elementIndex, + attributes.child(), + outputConfig, + serializersModule + ) + else -> { + val table = TomlTablePrimitive( + TomlKey(attributes.getFullKey(), elementIndex), + elementIndex, + attributes.comments, + attributes.inlineComment + ) + + rootNode.appendChild(table) + + tableEncoder(table) + } + } + + override fun endStructure(descriptor: SerialDescriptor) { + if (rootNode is TomlTablePrimitive && rootNode.hasNoChildren()) { + rootNode.appendChild(TomlStubEmptyNode(elementIndex)) + } + + // Put table children last to avoid the need for table redeclaration. + rootNode.children.sortBy { it is TomlTable } + + // Mark primitive tables as synthetic if their only children are nested + // tables, to avoid extraneous definition. + // Todo: Find a more elegant solution that doesn't make isSynthetic mutable. + if (!outputConfig.explicitTables && + rootNode is TomlTablePrimitive && + rootNode.children.all { it is TomlTable }) { + rootNode.isSynthetic = true + } + + super.endStructure(descriptor) + } + + public companion object { + /** + * Encodes the specified [value] into a [TomlFile]. + * + * @param serializer The user-defined or compiler-generated serializer for + * type [T]. + * @param value The value to serialize. + * @param outputConfig The output config. + * @param serializersModule + * @return The encoded [TomlFile] node. + */ + public fun encode( + serializer: SerializationStrategy, + value: T, + outputConfig: TomlOutputConfig = TomlOutputConfig(), + serializersModule: SerializersModule = EmptySerializersModule() + ): TomlFile { + val root = TomlFile() + + val encoder = TomlMainEncoder( + root, + outputConfig = outputConfig, + serializersModule = serializersModule + ) + + serializer.serialize(encoder, value) + + return root + } + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlDecodingException.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlDecodingException.kt index a0d0357c..dd59fba9 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlDecodingException.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlDecodingException.kt @@ -9,7 +9,13 @@ import kotlinx.serialization.descriptors.elementNames public sealed class TomlDecodingException(message: String) : SerializationException(message) -internal class ParseException(message: String, lineNo: Int) : TomlDecodingException("Line $lineNo: $message") +internal open class ParseException(message: String, lineNo: Int) : TomlDecodingException("Line $lineNo: $message") + +internal class UnknownEscapeSymbolsException(invalid: String, lineNo: Int) : ParseException( + "According to TOML documentation unknown" + + " escape symbols are not allowed. Please check: [\\$invalid]", + lineNo +) internal class InternalDecodingException(message: String) : TomlDecodingException(message) @@ -32,8 +38,6 @@ internal class NullValueException(propertyName: String, lineNo: Int) : TomlDecod " Please check the input (line: <$lineNo>) or make the property nullable" ) -internal class CastException(message: String, lineNo: Int) : TomlDecodingException("Line $lineNo: $message") - internal class IllegalTypeException(message: String, lineNo: Int) : TomlDecodingException("Line $lineNo: $message") internal class MissingRequiredPropertyException(message: String) : TomlDecodingException(message) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlEncodingException.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlEncodingException.kt index 8da96b97..eb9c0d06 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlEncodingException.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlEncodingException.kt @@ -7,3 +7,10 @@ import kotlinx.serialization.SerializationException public sealed class TomlEncodingException(message: String) : SerializationException(message) internal class TomlWritingException(message: String) : TomlEncodingException(message) + +internal class InternalEncodingException(message: String) : TomlEncodingException(message) + +// Todo: This needs a better name +internal class IllegalEncodingTypeException(message: String, lineNo: Int) : TomlEncodingException("Line $lineNo: $message") + +internal class UnsupportedEncodingFeatureException(message: String) : TomlEncodingException(message) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/StringUtils.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/StringUtils.kt index b735f098..d27ad41a 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/StringUtils.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/StringUtils.kt @@ -6,15 +6,6 @@ package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.exceptions.ParseException -/** - * method to find the beginning of the comments in TOML string - * - * @param startSearchFrom the index after that the search will be done - * @return the index of the first hash symbol or the index of the end of the string - */ -internal fun String.findBeginningOfTheComment(startSearchFrom: Int) = - (startSearchFrom until this.length).filter { this[it] == '#' }.minOrNull() ?: this.length - /** * Splitting dot-separated string to the list of tokens: * a.b.c -> [a, b, c]; a."b.c".d -> [a, "b.c", d]; @@ -67,6 +58,33 @@ internal fun String.splitKeyToTokens(lineNo: Int): List { */ internal fun String.trimSingleQuotes(): String = trimSymbols(this, "'", "'") +/** + * If this multiline string starts and end with triple quotes(''') - will return the string with + * quotes and newline removed. + * Otherwise, returns this string. + * + * @return string with the result + */ +internal fun String.trimMultilineLiteralQuotes(): String = trimSymbols(this, "'''", "'''").removePrefix("\n") + +/** + * When the last non-whitespace character on a line is an unescaped \, it will + * be trimmed along with all whitespace (including newlines) up to the next + * non-whitespace character or closing delimiter. + * + * @return string with the result + */ +internal fun String.convertLineEndingBackslash(): String { + // We shouldn't trim if the size of the split array == 1 + // It means there is no ending backslash, and we should keep all spaces + val splitEndingBackslash = this.split("\\\n") + return if (splitEndingBackslash.size == 1) { + this + } else { + splitEndingBackslash.joinToString("") { it.trimStart() } + } +} + /** * If this string starts and end with quotes("") - will return the string with quotes removed * Otherwise, returns this string. @@ -75,6 +93,15 @@ internal fun String.trimSingleQuotes(): String = trimSymbols(this, "'", "'") */ internal fun String.trimQuotes(): String = trimSymbols(this, "\"", "\"") +/** + * If this multiline string starts and end with triple quotes(""") - will return the string with + * quotes and newline removed. + * Otherwise, returns this string. + * + * @return string with the result + */ +internal fun String.trimMultilineQuotes(): String = trimSymbols(this, "\"\"\"", "\"\"\"").removePrefix("\n") + /** * If this string starts and end with curly braces ({}) - will return the string without them (used in inline tables) * Otherwise, returns this string. @@ -91,6 +118,14 @@ internal fun String.trimCurlyBraces(): String = trimSymbols(this, "{", "}") */ internal fun String.trimBrackets(): String = trimSymbols(this, "[", "]") +/** + * If this string ends with comma(,) - will return the string with trailing comma removed. + * Otherwise, returns this string. + * + * @return string with the result + */ +internal fun String.removeTrailingComma(): String = this.removeSuffix(",") + /** * If this string starts and end with a pair brackets([[]]) - will return the string with brackets removed * Otherwise, returns this string. @@ -99,6 +134,50 @@ internal fun String.trimBrackets(): String = trimSymbols(this, "[", "]") */ internal fun String.trimDoubleBrackets(): String = trimSymbols(this, "[[", "]]") +/** + * Takes only the text before a comment + * + * @param allowEscapedQuotesInLiteralStrings value from TomlInputConfig + * @return The text before a comment, i.e. + * ```kotlin + * "a = 0 # Comment".takeBeforeComment() == "a = 0" + * ``` + */ +internal fun String.takeBeforeComment(allowEscapedQuotesInLiteralStrings: Boolean): String { + val commentStartIndex = getCommentStartIndex(allowEscapedQuotesInLiteralStrings) + + return if (commentStartIndex == -1) { + this.trim() + } else { + this.substring(0, commentStartIndex).trim() + } +} + +/** + * Trims a comment of any text before it and its hash token. + * + * @param allowEscapedQuotesInLiteralStrings value from TomlInputConfig + * @return The comment text, i.e. + * ```kotlin + * "a = 0 # Comment".trimComment() == "Comment" + * ``` + */ +internal fun String.trimComment(allowEscapedQuotesInLiteralStrings: Boolean): String { + val commentStartIndex = getCommentStartIndex(allowEscapedQuotesInLiteralStrings) + + return if (commentStartIndex == -1) { + "" + } else { + drop(commentStartIndex + 1).trim() + } +} + +/** + * @param substring + * @return count of occurrences of substring in string + */ +internal fun String.getCountOfOccurrencesOfSubstring(substring: String): Int = this.split(substring).size - 1 + private fun String.validateSpaces(lineNo: Int, fullKey: String) { if (this.trim().count { it == ' ' } > 0 && this.isNotQuoted()) { throw ParseException( @@ -138,7 +217,7 @@ private fun String.validateSymbols(lineNo: Int) { throw ParseException( "Not able to parse the key: [$this] as it contains invalid symbols." + " In case you would like to use special symbols - use quotes as" + - " it is required by TOML standard: \"My key ~ with special % symbols\"", + " it is required by TOML standard: \"My key with special (%, ±) symbols\" = \"value\"", lineNo ) } @@ -146,6 +225,41 @@ private fun String.validateSymbols(lineNo: Int) { } } +private fun String.getCommentStartIndex(allowEscapedQuotesInLiteralStrings: Boolean): Int { + val isEscapingDisabled = if (allowEscapedQuotesInLiteralStrings) { + // escaping is disabled when the config option is true AND we have a literal string + val firstQuoteLetter = this.firstOrNull { it == '\"' || it == '\'' } + firstQuoteLetter == '\'' + } else { + false + } + + val chars = if (!isEscapingDisabled) { + this.replace("\\\"", "__") + .replace("\\\'", "__") + } else { + this + }.toCharArray() + var currentQuoteChar: Char? = null + + chars.forEachIndexed { idx, symbol -> + // take hash index if it's not enclosed in quotation marks + if (symbol == '#' && currentQuoteChar == null) { + return idx + } + + if (symbol == '\"' || symbol == '\'') { + if (currentQuoteChar == null) { + currentQuoteChar = symbol + } else if (currentQuoteChar == symbol) { + currentQuoteChar = null + } + } + } + + return -1 +} + private fun Char.isLetterOrDigit() = CharRange('A', 'Z').contains(this) || CharRange('a', 'z').contains(this) || CharRange('0', '9').contains(this) 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 656f47fa..4ce9e134 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 @@ -1,7 +1,8 @@ package com.akuleshov7.ktoml.parsers -import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.tree.* +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.exceptions.ParseException +import com.akuleshov7.ktoml.tree.nodes.* import kotlin.jvm.JvmInline /** @@ -9,7 +10,7 @@ import kotlin.jvm.JvmInline */ @JvmInline @Suppress("WRONG_MULTIPLE_MODIFIERS_ORDER") -public value class TomlParser(private val config: TomlConfig) { +public value class TomlParser(private val config: TomlInputConfig) { /** * Method for parsing of TOML string (this string should be split with newlines \n or \r\n) * @@ -32,16 +33,6 @@ public value class TomlParser(private val config: TomlConfig) { return parseStringsToTomlTree(tomlLines, config) } - /** - * Parsing the list of strings to the TOML intermediate representation (TOML- abstract syntax tree). - * - * @param tomlLines list with toml strings (line by line) - * @param config - * @return the root node of the resulted toml tree - * @throws InternalAstException - if toml node does not inherit TomlNode class - */ - public fun parseStringsToTomlTree(tomlLines: List, config: TomlConfig): TomlFile = parseStringsToTomlTree(tomlLines.asSequence(), config) - /** * Parsing the list of strings to the TOML intermediate representation (TOML- abstract syntax tree). * @@ -51,8 +42,8 @@ public value class TomlParser(private val config: TomlConfig) { * @throws InternalAstException - if toml node does not inherit TomlNode class */ @Suppress("TOO_LONG_FUNCTION", "NESTED_BLOCK") - public fun parseStringsToTomlTree(tomlLines: Sequence, config: TomlConfig): TomlFile { - var currentParentalNode: TomlNode = TomlFile(config) + public fun parseStringsToTomlTree(tomlLines: Sequence, config: TomlInputConfig): TomlFile { + var currentParentalNode: TomlNode = TomlFile() // link to the head of the tree val tomlFileHead = currentParentalNode as TomlFile // need to trim empty lines BEFORE the start of processing @@ -60,22 +51,50 @@ public value class TomlParser(private val config: TomlConfig) { // here we always store the bucket of the latest created array of tables var latestCreatedBucket: TomlArrayOfTablesElement? = null + val comments: MutableList = mutableListOf() var index = 0 val linesIterator = trimmedTomlLines.iterator() // all lines will be streamed sequentially while (linesIterator.hasNext()) { val line = linesIterator.next() val lineNo = index + 1 + // comments and empty lines can easily be ignored in the TomlTree, but we cannot filter them out in mutableTomlLines // because we need to calculate and save lineNo - if (!line.isComment() && !line.isEmptyLine()) { - if (line.isTableNode()) { - if (line.isArrayOfTables()) { + if (line.isComment()) { + comments += line.trimComment(config.allowEscapedQuotesInLiteralStrings) + } else if (!line.isEmptyLine()) { + // Parse the inline comment if any + val inlineComment = line.trimComment(config.allowEscapedQuotesInLiteralStrings) + + val multilineType = line.getMultilineType() + val tomlLine = if (multilineType != MultilineType.NOT_A_MULTILINE) { + // first line from multiline is already taken from the sequence and can be processed + val collectedMultiline = StringBuilder() + collectLineWithComments(collectedMultiline, comments, multilineType, line) + collectedMultiline.append("\n") + + // processing remaining lines from a multiline + val indexAtTheEndOfMultiline = collectMultiline( + linesIterator, + collectedMultiline, + index, + multilineType, + comments + ) + index = indexAtTheEndOfMultiline + collectedMultiline.toString() + } else { + line + } + + if (tomlLine.isTableNode()) { + if (tomlLine.isArrayOfTables()) { // TomlArrayOfTables contains all information about the ArrayOfTables ([[array of tables]]) - val tableArray = TomlArrayOfTables(line, lineNo, config) + val tableArray = TomlArrayOfTables(tomlLine, lineNo) val arrayOfTables = tomlFileHead.insertTableToTree(tableArray, latestCreatedBucket) // creating a new empty element that will be used as an element in array and the parent for next key-value records - val newArrayElement = TomlArrayOfTablesElement(lineNo, config) + val newArrayElement = TomlArrayOfTablesElement(lineNo, comments, inlineComment) // adding this element as a child to the array of tables arrayOfTables.appendChild(newArrayElement) // covering the case when the processed table does not contain nor key-value pairs neither tables (after our insertion) @@ -86,11 +105,11 @@ public value class TomlParser(private val config: TomlConfig) { // here we set the bucket that will be incredibly useful when we will be inserting the next array of tables latestCreatedBucket = newArrayElement } else { - val tableSection = TomlTablePrimitive(line, lineNo, config) + val tableSection = TomlTablePrimitive(tomlLine, lineNo, comments, inlineComment) // if the table is the last line in toml, then it has no children, and we need to // add at least fake node as a child if (!linesIterator.hasNext()) { - tableSection.appendChild(TomlStubEmptyNode(lineNo, config)) + tableSection.appendChild(TomlStubEmptyNode(lineNo)) } // covering the case when the processed table does not contain nor key-value pairs neither tables (after our insertion) // adding fake nodes to a previous table (it has no children because we have found another table right after) @@ -98,14 +117,14 @@ public value class TomlParser(private val config: TomlConfig) { currentParentalNode = tomlFileHead.insertTableToTree(tableSection) } } else { - val keyValue = line.parseTomlKeyValue(lineNo, config) + val keyValue = tomlLine.parseTomlKeyValue(lineNo, comments, inlineComment, config) // inserting the key-value record to the tree when { keyValue is TomlKeyValue && keyValue.key.isDotted -> // in case parser has faced dot-separated complex key (a.b.c) it should create proper table [a.b], // because table is the same as dotted key tomlFileHead - .insertTableToTree(keyValue.createTomlTableFromDottedKey(currentParentalNode, config)) + .insertTableToTree(keyValue.createTomlTableFromDottedKey(currentParentalNode)) .appendChild(keyValue) keyValue is TomlInlineTable -> @@ -117,19 +136,13 @@ public value class TomlParser(private val config: TomlConfig) { else -> currentParentalNode.appendChild(keyValue) } } + comments.clear() } index++ } - return tomlFileHead } - private fun TomlNode.insertStub() { - if (this.hasNoChildren() && this !is TomlFile && this !is TomlArrayOfTablesElement) { - this.appendChild(TomlStubEmptyNode(this.lineNo, config)) - } - } - @Suppress("TOO_LONG_FUNCTION") // This code is heavily inspired by the TransformingSequence code in kotlin-lib, simple trimming of empty lines private fun Sequence.trimEmptyLines(): Sequence = object : Sequence { @@ -199,6 +212,120 @@ public value class TomlParser(private val config: TomlConfig) { } } + /** + * @param collectedMultiline append all multi-lines to this argument + * @return index at the end of multiline + */ + private fun collectMultiline( + linesIterator: Iterator, + collectedMultiline: StringBuilder, + startIndex: Int, + multilineType: MultilineType, + comments: MutableList + ): Int { + var index = startIndex + var line: String + var hasFoundEnd = false + + // all lines will be streamed sequentially + while (linesIterator.hasNext()) { + line = linesIterator.next() + collectLineWithComments(collectedMultiline, comments, multilineType, line) + + if (line.isEndOfMultilineValue(multilineType, index + 1)) { + hasFoundEnd = true + break + } + // append new line to collect string as is + collectedMultiline.append("\n") + index++ + } + + if (!hasFoundEnd) { + throw ParseException( + "Expected (${multilineType.closingSymbols}) in the end of ${multilineType.name}", + startIndex + 1 + ) + } + return index + } + + private fun collectLineWithComments( + collectTo: StringBuilder, + comments: MutableList, + multilineType: MultilineType, + line: String + ) { + if (multilineType == MultilineType.ARRAY) { + collectTo.append(line.takeBeforeComment(config.allowEscapedQuotesInLiteralStrings)) + comments += line.trimComment(config.allowEscapedQuotesInLiteralStrings) + } else { + // we can't have comments inside a multi-line basic/literal string + collectTo.append(line) + } + } + + /** + * Important! We treat a multi-line that is declared in one line ("""abc""") as a regular not multiline string + */ + private fun String.getMultilineType(): MultilineType { + val line = this.takeBeforeComment(config.allowEscapedQuotesInLiteralStrings) + val firstEqualsSign = line.indexOfFirst { it == '=' } + if (firstEqualsSign == -1) { + return MultilineType.NOT_A_MULTILINE + } + val value = line.substring(firstEqualsSign + 1).trim() + + if (value.startsWith("[") && !value.endsWith("]")) { + return MultilineType.ARRAY + } + + // If we have more than 1 combination of (""") - it means that + // multi-line is declared in one line, and we can handle it as not a multi-line + if (value.startsWith("\"\"\"") && value.getCountOfOccurrencesOfSubstring("\"\"\"") == 1) { + return MultilineType.BASIC_STRING + } + if (value.startsWith("'''") && value.getCountOfOccurrencesOfSubstring("\'\'\'") == 1) { + return MultilineType.LITERAL_STRING + } + + // Otherwise, the string isn't a multi-line declaration + return MultilineType.NOT_A_MULTILINE + } + + /** + * @return true if string is a last line of multiline value declaration + */ + private fun String.isEndOfMultilineValue(multilineType: MultilineType, lineNo: Int): Boolean { + if (multilineType == MultilineType.NOT_A_MULTILINE) { + throw ParseException("Internal parse exception", lineNo) + } + + return this.takeBeforeComment(config.allowEscapedQuotesInLiteralStrings) + .trim() + .endsWith(multilineType.closingSymbols) + } + + private fun TomlNode.insertStub() { + if (this.hasNoChildren() && this !is TomlFile && this !is TomlArrayOfTablesElement) { + this.appendChild(TomlStubEmptyNode(this.lineNo)) + } + } + + private fun MutableList.trimEmptyTrailingLines(): MutableList { + if (this.isEmpty()) { + return this + } + // removing all empty lines at the end, to cover empty tables properly + while (this.last().isEmptyLine()) { + this.removeLast() + if (this.isEmpty()) { + return this + } + } + return this + } + private fun String.isArrayOfTables(): Boolean = this.trim().startsWith("[[") private fun String.isTableNode(): Boolean { @@ -209,20 +336,38 @@ public value class TomlParser(private val config: TomlConfig) { private fun String.isComment() = this.trim().startsWith("#") private fun String.isEmptyLine() = this.trim().isEmpty() + + /** + * @property closingSymbols - symbols indicating that the multi-line is closed + */ + private enum class MultilineType(val closingSymbols: String) { + ARRAY("]"), + BASIC_STRING("\"\"\""), + LITERAL_STRING("'''"), + NOT_A_MULTILINE(""), + ; + } } /** * factory adaptor to split the logic of parsing simple values from the logic of parsing collections (like Arrays) * * @param lineNo + * @param comments + * @param inlineComment * @param config * @return parsed toml node */ -public fun String.parseTomlKeyValue(lineNo: Int, config: TomlConfig): TomlNode { +public fun String.parseTomlKeyValue( + lineNo: Int, + comments: List, + inlineComment: String, + config: TomlInputConfig +): TomlNode { val keyValuePair = this.splitKeyValue(lineNo, config) return when { - keyValuePair.second.startsWith("[") -> TomlKeyValueArray(keyValuePair, lineNo, config) - keyValuePair.second.startsWith("{") -> TomlInlineTable(keyValuePair, lineNo, config) - else -> TomlKeyValuePrimitive(keyValuePair, lineNo, config) + keyValuePair.second.startsWith("[") -> TomlKeyValueArray(keyValuePair, lineNo, comments, inlineComment, config) + keyValuePair.second.startsWith("{") -> TomlInlineTable(keyValuePair, lineNo, comments, inlineComment, config) + else -> TomlKeyValuePrimitive(keyValuePair, lineNo, comments, inlineComment, config) } } diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlArrayOfTables.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlArrayOfTables.kt deleted file mode 100644 index dbcdbde4..00000000 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlArrayOfTables.kt +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Array of tables https://toml.io/en/v1.0.0#array-of-tables - */ - -package com.akuleshov7.ktoml.tree - -import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.exceptions.ParseException -import com.akuleshov7.ktoml.parsers.findBeginningOfTheComment -import com.akuleshov7.ktoml.parsers.splitKeyToTokens -import com.akuleshov7.ktoml.parsers.trimDoubleBrackets -import com.akuleshov7.ktoml.parsers.trimQuotes -import com.akuleshov7.ktoml.writers.TomlEmitter - -/** - * Class representing array of tables - * - * @throws ParseException if the content is wrong - */ -// FixMe: this class is mostly identical to the TomlTable - we should unify them together -public class TomlArrayOfTables( - content: String, - lineNo: Int, - config: TomlConfig = TomlConfig(), - isSynthetic: Boolean = false -) : TomlTable( - content, - lineNo, - config, - isSynthetic -) { - public override val type: TableType = TableType.ARRAY - - // short table name (only the name without parental prefix, like a - it is used in decoder and encoder) - override val name: String - - // list of tables (including sub-tables) that are included in this table (e.g.: {a, a.b, a.b.c} in a.b.c) - public override lateinit var tablesList: List - - // full name of the table (like a.b.c.d) - public override lateinit var fullTableName: String - - init { - val lastIndexOfBrace = content.lastIndexOf("]]") - if (lastIndexOfBrace == -1) { - throw ParseException("Invalid Array of Tables provided: $content." + - " It has missing closing brackets: ']]'", lineNo) - } - - // finding the index of the beginning of the comment (if any) - val firstHash = content.findBeginningOfTheComment(lastIndexOfBrace) - - // getting the content inside brackets ([a.b] -> a.b) - val sectionFromContent = content.substring(0, firstHash).trim().trimDoubleBrackets() - .trim() - - if (sectionFromContent.isBlank()) { - throw ParseException("Incorrect blank name for array of tables: $content", lineNo) - } - - fullTableName = sectionFromContent - - val sectionsList = sectionFromContent.splitKeyToTokens(lineNo) - name = sectionsList.last().trimQuotes() - tablesList = sectionsList.mapIndexed { index, _ -> - (0..index).joinToString(".") { sectionsList[it] } - } - } - - override fun TomlEmitter.writeHeader(headerKey: TomlKey, config: TomlConfig) { - startTableArrayHeader() - - headerKey.write(emitter = this, config) - - endTableArrayHeader() - } - - override fun TomlEmitter.writeChildren( - headerKey: TomlKey, - children: List, - config: TomlConfig, - multiline: Boolean - ) { - val last = children.lastIndex - - children.forEachIndexed { i, child -> - if (child is TomlArrayOfTablesElement) { - if (parent !is TomlArrayOfTablesElement) { - emitIndent() - } - - writeHeader(headerKey, config) - - if (!child.hasNoChildren()) { - emitNewLine() - } - - indent() - - child.write(emitter = this, config, multiline) - - dedent() - - if (i < last) { - emitNewLine() - - // Primitive pairs have a single newline after, except when a - // table follows. - if (child !is TomlKeyValuePrimitive || children[i + 1] is TomlTable) { - emitNewLine() - } - } - } else { - child.write(emitter = this, config, multiline) - } - } - } -} - -/** - * This class is used to store elements of array of tables (bucket for key-value records) - */ -public class TomlArrayOfTablesElement(lineNo: Int, config: TomlConfig = TomlConfig()) : TomlNode( - EMPTY_TECHNICAL_NODE, - lineNo, - config -) { - override val name: String = EMPTY_TECHNICAL_NODE - - override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ): Unit = emitter.writeChildren(children, config, multiline) -} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlFile.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlFile.kt deleted file mode 100644 index 3dadd811..00000000 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlFile.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.akuleshov7.ktoml.tree - -import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.exceptions.InternalAstException -import com.akuleshov7.ktoml.writers.TomlEmitter - -/** - * A root node for TOML Abstract Syntax Tree - */ -public class TomlFile(config: TomlConfig = TomlConfig()) : TomlNode( - "rootNode", - 0, - config -) { - override val name: String = "rootNode" - - override fun getNeighbourNodes(): MutableList = - throw InternalAstException("Invalid call to getNeighbourNodes() for TomlFile node") - - override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ): Unit = - emitter.writeChildren( - children, - config, - multiline - ) -} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlInlineTable.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlInlineTable.kt deleted file mode 100644 index dc8c8013..00000000 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlInlineTable.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.akuleshov7.ktoml.tree - -import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.exceptions.ParseException -import com.akuleshov7.ktoml.parsers.parseTomlKeyValue -import com.akuleshov7.ktoml.parsers.trimCurlyBraces -import com.akuleshov7.ktoml.writers.TomlEmitter - -/** - * Class for parsing and representing of inline tables: inline = { a = 5, b = 6 , c = 7 } - * @property lineNo line number - * @property keyValuePair parsed keyValue - * @property config toml configuration - */ -public class TomlInlineTable( - private val keyValuePair: Pair, - lineNo: Int, - config: TomlConfig = TomlConfig(), -) : TomlNode( - "${keyValuePair.first} = ${keyValuePair.second}", - lineNo, - config -) { - override val name: String = keyValuePair.first - private val tomlKeyValues: List - - init { - tomlKeyValues = keyValuePair.second.parseInlineTableValue() - } - - private fun String.parseInlineTableValue(): List { - val parsedList = this - .trimCurlyBraces() - .trim() - .also { - if (it.endsWith(",")) { - throw ParseException( - "Trailing commas are not permitted in inline tables: [${keyValuePair.second}] ", lineNo - ) - } - } - .split(",") - .map { it.parseTomlKeyValue(lineNo, config) } - - return parsedList - } - - public fun returnTable(tomlFileHead: TomlFile, currentParentalNode: TomlNode): TomlTable { - val tomlTable = TomlTablePrimitive( - "[${if (currentParentalNode is TomlTable) "${currentParentalNode.fullTableName}." else ""}${keyValuePair.first}]", - lineNo, - config - ) - - // FixMe: this code duplication can be unified with the logic in TomlParser - tomlKeyValues.forEach { keyValue -> - when { - keyValue is TomlKeyValue && keyValue.key.isDotted -> { - // in case parser has faced dot-separated complex key (a.b.c) it should create proper table [a.b], - // because table is the same as dotted key - val newTableSection = keyValue.createTomlTableFromDottedKey(tomlTable, config) - - tomlFileHead - .insertTableToTree(newTableSection) - .appendChild(keyValue) - } - - keyValue is TomlInlineTable -> tomlFileHead.insertTableToTree( - keyValue.returnTable(tomlFileHead, tomlTable) - ) - - // otherwise, it should simply append the keyValue to the parent - else -> tomlTable.appendChild(keyValue) - - } - } - return tomlTable - } - - public override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ) { - val key = TomlKey(name, 0) - - key.write(emitter, config) - - emitter.emitPairDelimiter() - .startInlineTable() - - tomlKeyValues.forEachIndexed { i, pair -> - if (i > 0) { - emitter.emitElementDelimiter() - } - - emitter.emitWhitespace() - - pair.write(emitter, config) - } - - emitter.emitWhitespace() - .endInlineTable() - } -} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKey.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKey.kt deleted file mode 100644 index a486014c..00000000 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKey.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.akuleshov7.ktoml.tree - -import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.exceptions.TomlWritingException -import com.akuleshov7.ktoml.parsers.splitKeyToTokens -import com.akuleshov7.ktoml.parsers.trimQuotes -import com.akuleshov7.ktoml.writers.TomlEmitter - -/** - * Class that represents a toml key-value pair. - * Key has TomlKey type, Value has TomlValue type - * - * @property rawContent - * @property lineNo - */ -public class TomlKey(public val rawContent: String, public val lineNo: Int) { - internal val keyParts = rawContent.splitKeyToTokens(lineNo) - public val content: String = keyParts.last().trimQuotes().trim() - internal val isDotted = isDottedKey() - - /** - * checking that we face a key in the following format: a."ab.c".my-key - * - * @return true if the key is in dotted format (a.b.c) - */ - private fun isDottedKey(): Boolean { - var singleQuoteIsClosed = true - var doubleQuoteIsClosed = true - rawContent.forEach { ch -> - when (ch) { - '\'' -> singleQuoteIsClosed = !singleQuoteIsClosed - '\"' -> doubleQuoteIsClosed = !doubleQuoteIsClosed - else -> { - // this is a generated else block - } - } - - if (ch == '.' && doubleQuoteIsClosed && singleQuoteIsClosed) { - return true - } - } - return false - } - - public fun write(emitter: TomlEmitter, config: TomlConfig) { - val keys = keyParts - - if (keys.isEmpty() || keys.any(String::isEmpty)) { - throw TomlWritingException( - "Empty keys are not allowed: the key at line $lineNo is empty or" + - " has an empty key part." - ) - } - - keys.forEachIndexed { i, value -> - if (i > 0) { - emitter.emitKeyDot() - } - - when { - value.startsWith('"') && value.endsWith('"') -> - emitter.emitQuotedKey(value.substring(1 until value.lastIndex)) - value.startsWith('\'') && value.endsWith('\'') -> - emitter.emitQuotedKey(value.substring(1 until value.lastIndex), isLiteral = true) - else -> emitter.emitBareKey(value) - } - } - } -} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValueArray.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValueArray.kt deleted file mode 100644 index 37aa681f..00000000 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValueArray.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.akuleshov7.ktoml.tree - -import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.writers.TomlEmitter - -/** - * Class for parsing and storing Array of Tables in AST. - * @property lineNo - * @property key - * @property value - * @property name - */ -public class TomlKeyValueArray( - override var key: TomlKey, - override val value: TomlValue, - override val lineNo: Int, - override val name: String, - config: TomlConfig = TomlConfig() -) : TomlNode( - key, - value, - lineNo, - config -), TomlKeyValue { - // adaptor for a string pair of key-value - public constructor( - keyValuePair: Pair, - lineNo: Int, - config: TomlConfig = TomlConfig() - ) : this( - TomlKey(keyValuePair.first, lineNo), - keyValuePair.second.parseList(lineNo, config), - lineNo, - TomlKey(keyValuePair.first, lineNo).content - ) - - public override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ): Unit = super.write(emitter, config, multiline) -} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValuePrimitive.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValuePrimitive.kt deleted file mode 100644 index 603eee3a..00000000 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValuePrimitive.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.akuleshov7.ktoml.tree - -import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.writers.TomlEmitter - -/** - * class for parsing and storing simple single value types in AST - * @property lineNo - * @property key - * @property value - * @property name - */ -public class TomlKeyValuePrimitive( - override var key: TomlKey, - override val value: TomlValue, - override val lineNo: Int, - override val name: String, - config: TomlConfig = TomlConfig() -) : TomlNode( - key, - value, - lineNo, - config -), TomlKeyValue { - // adaptor for a string pair of key-value - public constructor( - keyValuePair: Pair, - lineNo: Int, - config: TomlConfig = TomlConfig() - ) : this( - TomlKey(keyValuePair.first, lineNo), - keyValuePair.second.parseValue(lineNo, config), - lineNo, - TomlKey(keyValuePair.first, lineNo).content - ) - - public override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ): Unit = super.write(emitter, config, multiline) -} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlTable.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlTable.kt deleted file mode 100644 index 356a6a28..00000000 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlTable.kt +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Common class for tables - */ - -package com.akuleshov7.ktoml.tree - -import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.writers.TomlEmitter - -/** - * Abstract class to represent all types of tables: primitive/arrays/etc. - * @property content - raw string name of the table - * @property lineNo - line number - * @property config - toml configuration - * @property isSynthetic - */ -@Suppress("COMMENT_WHITE_SPACE") -public abstract class TomlTable( - override val content: String, - override val lineNo: Int, - override val config: TomlConfig = TomlConfig(), - public val isSynthetic: Boolean = false -) : TomlNode( - content, - lineNo, - config -) { - public abstract var fullTableName: String - public abstract var tablesList: List - public abstract val type: TableType - - override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ) { - // Todo: Option to explicitly define super tables? - // Todo: Support dotted key-value pairs (i.e. a.b.c.d = 7) - - val key = TomlKey(fullTableName, 0) - - val firstChild = children.first() - - if (isExplicit(firstChild) && type == TableType.PRIMITIVE) { - emitter.writeHeader(key, config) - - if (firstChild !is TomlStubEmptyNode) { - emitter.emitNewLine() - } - - emitter.indent() - - emitter.writeChildren(key, children, config, multiline) - - emitter.dedent() - } else { - emitter.writeChildren(key, children, config, multiline) - } - } - - protected abstract fun TomlEmitter.writeChildren( - headerKey: TomlKey, - children: List, - config: TomlConfig, - multiline: Boolean - ) - - protected abstract fun TomlEmitter.writeHeader( - headerKey: TomlKey, - config: TomlConfig - ) - - /** - * Determines whether the table should be explicitly defined. Synthetic tables - * are implicit except when: - * - the first child is a pair, and there are other children (to exclude - * dotted pairs) - * - the table is empty - */ - private fun isExplicit( - firstChild: TomlNode - ) = if (!isSynthetic) { - true - } else { - when (firstChild) { - is TomlStubEmptyNode -> true - is TomlKeyValue, - is TomlInlineTable -> children.size > 1 - else -> false - } - } -} - -/** - * Special Enum that is used in a logic related to insertion of tables to AST - */ -public enum class TableType { - ARRAY, - PRIMITIVE, - ; -} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlTablePrimitive.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlTablePrimitive.kt deleted file mode 100644 index b7a5cee8..00000000 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlTablePrimitive.kt +++ /dev/null @@ -1,129 +0,0 @@ -/** - * File contains all classes used in Toml AST node - */ - -package com.akuleshov7.ktoml.tree - -import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.exceptions.ParseException -import com.akuleshov7.ktoml.parsers.findBeginningOfTheComment -import com.akuleshov7.ktoml.parsers.splitKeyToTokens -import com.akuleshov7.ktoml.parsers.trimBrackets -import com.akuleshov7.ktoml.parsers.trimQuotes -import com.akuleshov7.ktoml.writers.TomlEmitter - -/** - * tablesList - a list of names of sections (tables) that are included into this particular TomlTable - * for example: if the TomlTable is [a.b.c] this list will contain [a], [a.b], [a.b.c] - * @property isSynthetic - flag to determine that this node was synthetically and there is no such table in the input - */ -@Suppress("MULTIPLE_INIT_BLOCKS") -public class TomlTablePrimitive( - content: String, - lineNo: Int, - config: TomlConfig = TomlConfig(), - isSynthetic: Boolean = false -) : TomlTable( - content, - lineNo, - config, - isSynthetic -) { - public override val type: TableType = TableType.PRIMITIVE - - // short table name (only the name without parental prefix, like a - it is used in decoder and encoder) - override val name: String - - // list of tables (including sub-tables) that are included in this table (e.g.: {a, a.b, a.b.c} in a.b.c) - public override lateinit var tablesList: List - - // full name of the table (like a.b.c.d) - public override lateinit var fullTableName: String - - init { - val lastIndexOfBrace = content.lastIndexOf("]") - if (lastIndexOfBrace == -1) { - throw ParseException("Invalid Tables provided: $content." + - " It has missing closing bracket: ']'", lineNo) - } - - // finding the index of the beginning of the comment (if any) - val firstHash = content.findBeginningOfTheComment(lastIndexOfBrace) - - // getting the content inside brackets ([a.b] -> a.b) - val sectionFromContent = content.substring(0, firstHash).trim().trimBrackets() - .trim() - - if (sectionFromContent.isBlank()) { - throw ParseException("Incorrect blank table name: $content", lineNo) - } - - fullTableName = sectionFromContent - - val sectionsList = sectionFromContent.splitKeyToTokens(lineNo) - name = sectionsList.last().trimQuotes() - tablesList = sectionsList.mapIndexed { index, _ -> - (0..index).joinToString(".") { sectionsList[it] } - } - } - - override fun TomlEmitter.writeHeader( - headerKey: TomlKey, - config: TomlConfig - ) { - startTableHeader() - - headerKey.write(emitter = this, config) - - endTableHeader() - } - - override fun TomlEmitter.writeChildren( - headerKey: TomlKey, - children: List, - config: TomlConfig, - multiline: Boolean - ) { - if (children.first() is TomlStubEmptyNode) { - return - } - - val last = children.lastIndex - - var prevChild: TomlNode? = null - - children.forEachIndexed { i, child -> - // Declare the super table after a nested table, to avoid a pair being - // a part of the previous table by mistake. - if ((child is TomlKeyValue || child is TomlInlineTable) && - prevChild is TomlTable) { - dedent() - - emitIndent() - writeHeader(headerKey, config) - emitNewLine() - - indent() - indent() - } - - if (child !is TomlTable || (!child.isSynthetic && child.getFirstChild() !is TomlTable)) { - emitIndent() - } - - child.write(emitter = this, config, multiline) - - if (i < last) { - emitNewLine() - - // Primitive pairs have a single newline after, except when a table - // follows. - if (child !is TomlKeyValuePrimitive || children[i + 1] is TomlTable) { - emitNewLine() - } - } - - prevChild = child - } - } -} 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 deleted file mode 100644 index 942a9b04..00000000 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt +++ /dev/null @@ -1,575 +0,0 @@ -/** - * All representations of TOML value nodes are stored in this file - */ - -package com.akuleshov7.ktoml.tree - -import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.exceptions.ParseException -import com.akuleshov7.ktoml.exceptions.TomlWritingException -import com.akuleshov7.ktoml.parsers.trimBrackets -import com.akuleshov7.ktoml.parsers.trimQuotes -import com.akuleshov7.ktoml.parsers.trimSingleQuotes -import com.akuleshov7.ktoml.utils.appendCodePointCompat -import com.akuleshov7.ktoml.utils.controlCharacterRegex -import com.akuleshov7.ktoml.utils.unescapedBackslashRegex -import com.akuleshov7.ktoml.writers.TomlEmitter -import kotlinx.datetime.* - -/** - * Base class for all nodes that represent values - * @property lineNo - line number of original file - */ -public sealed class TomlValue(public val lineNo: Int) { - public abstract var content: Any - - /** - * Writes this value to the specified [emitter], optionally writing the value - * [multiline] (if supported by the value type). - * - * @param emitter - * @param config - * @param multiline - */ - public abstract fun write( - emitter: TomlEmitter, - config: TomlConfig = TomlConfig(), - multiline: Boolean = false - ) -} - -/** - * 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( - override var content: Any, - lineNo: Int -) : TomlValue(lineNo) { - public constructor( - content: String, - lineNo: Int, - config: TomlConfig = TomlConfig() - ) : this(content.verifyAndTrimQuotes(lineNo, config), lineNo) - - override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ) { - if (multiline) { - throw TomlWritingException( - "Multiline strings are not yet supported." - ) - } - - val content = content as String - - emitter.emitValue( - content.escapeQuotesAndVerify(config), - isLiteral = true, - multiline - ) - } - - public companion object { - private fun String.verifyAndTrimQuotes(lineNo: Int, config: TomlConfig): Any = - if (startsWith("'") && endsWith("'")) { - val contentString = trimSingleQuotes() - if (config.allowEscapedQuotesInLiteralStrings) contentString.convertSingleQuotes() else contentString - } else { - throw ParseException( - "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("\\'", "'") - - private fun String.escapeQuotesAndVerify(config: TomlConfig) = - when { - controlCharacterRegex in this -> - throw TomlWritingException( - "Control characters (excluding tab) are not permitted" + - " in literal strings." - ) - '\\' in this -> - throw TomlWritingException( - "Escapes are not allowed in literal strings." - ) - '\'' in this -> - if (config.allowEscapedQuotesInLiteralStrings) { - replace("'", "\\'") - } else { - throw TomlWritingException( - "Single quotes are not permitted in literal string" + - " by default. Set allowEscapedQuotesInLiteral" + - "Strings to true in the config to ignore this." - ) - } - else -> this - } - } -} - -/** - * 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( - override var content: Any, - lineNo: Int -) : TomlValue(lineNo) { - public constructor( - content: String, - lineNo: Int - ) : this(content.verifyAndTrimQuotes(lineNo), lineNo) - - override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ) { - if (multiline) { - throw TomlWritingException( - "Multiline strings are not yet supported." - ) - } - - val content = content as String - - emitter.emitValue( - content.escapeSpecialCharacters(), - isLiteral = false, - multiline - ) - } - - public companion object { - private const val COMPLEX_UNICODE_LENGTH = 8 - private const val COMPLEX_UNICODE_PREFIX = 'U' - private const val HEX_RADIX = 16 - private const val SIMPLE_UNICODE_LENGTH = 4 - private const val SIMPLE_UNICODE_PREFIX = 'u' - - private fun String.verifyAndTrimQuotes(lineNo: Int): Any = - if (startsWith("\"") && endsWith("\"")) { - trimQuotes() - .checkOtherQuotesAreEscaped(lineNo) - .convertSpecialCharacters(lineNo) - } else { - throw ParseException( - "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 -> - if (ch == '\"' && (index == 0 || this[index - 1] != '\\')) { - throw ParseException( - "Found invalid quote that is not escaped." + - " Please remove the quote or use escaping" + - " in <$this> at position = [$index].", lineNo - ) - } - } - return this - } - - private fun String.convertSpecialCharacters(lineNo: Int): String { - val resultString = StringBuilder() - var i = 0 - while (i < length) { - val currentChar = get(i) - var offset = 1 - if (currentChar == '\\' && i != lastIndex) { - // Escaped - val next = get(i + 1) - offset++ - when (next) { - 't' -> resultString.append('\t') - 'b' -> resultString.append('\b') - 'r' -> resultString.append('\r') - 'n' -> resultString.append('\n') - '\\' -> resultString.append('\\') - '\'' -> resultString.append('\'') - '"' -> resultString.append('"') - SIMPLE_UNICODE_PREFIX, COMPLEX_UNICODE_PREFIX -> - offset += resultString.appendEscapedUnicode(this, next, i + 2, lineNo) - else -> throw ParseException( - "According to TOML documentation unknown" + - " escape symbols are not allowed. Please check: [\\$next]", - lineNo - ) - } - } else { - resultString.append(currentChar) - } - i += offset - } - return resultString.toString() - } - - private fun StringBuilder.appendEscapedUnicode( - fullString: String, - marker: Char, - codeStartIndex: Int, - lineNo: Int - ): Int { - val nbUnicodeChars = if (marker == SIMPLE_UNICODE_PREFIX) { - SIMPLE_UNICODE_LENGTH - } else { - COMPLEX_UNICODE_LENGTH - } - if (codeStartIndex + nbUnicodeChars > fullString.length) { - val invalid = fullString.substring(codeStartIndex - 1) - throw ParseException( - "According to TOML documentation unknown" + - " escape symbols are not allowed. Please check: [\\$invalid]", - lineNo - ) - } - val hexCode = fullString.substring(codeStartIndex, codeStartIndex + nbUnicodeChars) - val codePoint = hexCode.toInt(HEX_RADIX) - try { - appendCodePointCompat(codePoint) - } catch (e: IllegalArgumentException) { - throw ParseException( - "According to TOML documentation unknown" + - " escape symbols are not allowed. Please check: [\\$marker$hexCode]", - lineNo - ) - } - return nbUnicodeChars - } - - private fun String.escapeSpecialCharacters(): String { - val withCtrlCharsEscaped = replace(controlCharacterRegex) { match -> - when (val char = match.value.single()) { - '\b' -> "\\b" - '\n' -> "\\n" - '\u000C' -> "\\f" - '\r' -> "\\r" - else -> { - val code = char.code - - val hexDigits = code.toString(HEX_RADIX) - - "\\$SIMPLE_UNICODE_PREFIX${ - hexDigits.padStart(SIMPLE_UNICODE_LENGTH, '0') - }" - } - } - } - - return withCtrlCharsEscaped.replace( - unescapedBackslashRegex, - Regex.escapeReplacement("\\\\") - ) - } - } -} - -/** - * Toml AST Node for a representation of Arbitrary 64-bit signed integers: key = 1 - * @property content - */ -public class TomlLong -internal constructor( - override var content: Any, - lineNo: Int -) : TomlValue(lineNo) { - public constructor(content: String, lineNo: Int) : this(content.toLong(), lineNo) - - override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ) { - emitter.emitValue(content as Long) - } -} - -/** - * 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( - override var content: Any, - lineNo: Int -) : TomlValue(lineNo) { - public constructor(content: String, lineNo: Int) : this(content.toDouble(), lineNo) - - override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ) { - emitter.emitValue(content as Double) - } -} - -/** - * Toml AST Node for a representation of boolean types: key = true | false - * @property content - */ -public class TomlBoolean -internal constructor( - override var content: Any, - lineNo: Int -) : TomlValue(lineNo) { - public constructor(content: String, lineNo: Int) : this(content.toBoolean(), lineNo) - - override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ) { - emitter.emitValue(content as Boolean) - } -} - -/** - * Toml AST Node for a representation of date-time types (offset date-time, local date-time, local date) - * @property content - */ -public class TomlDateTime -internal constructor( - override var content: Any, - lineNo: Int -) : TomlValue(lineNo) { - public constructor(content: String, lineNo: Int) : this(content.trim().parseToDateTime(), lineNo) - - override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ) { - when (val content = content) { - is Instant -> emitter.emitValue(content) - is LocalDateTime -> emitter.emitValue(content) - is LocalDate -> emitter.emitValue(content) - else -> - throw TomlWritingException( - "Unknown date type ${content::class.simpleName}" - ) - } - } - - public companion object { - private fun String.parseToDateTime(): Any = try { - // Offset date-time - toInstant() - } catch (e: IllegalArgumentException) { - try { - // TOML spec allows a space instead of the T, try replacing the first space by a T - replaceFirst(' ', 'T').toInstant() - } catch (e: IllegalArgumentException) { - try { - // Local date-time - toLocalDateTime() - } catch (e: IllegalArgumentException) { - // Local date - toLocalDate() - } - } - } - } -} - -/** - * Toml AST Node for a representation of null: - * null, nil, NULL, NIL or empty (key = ) - */ -public class TomlNull(lineNo: Int) : TomlValue(lineNo) { - override var content: Any = "null" - - override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ) { - emitter.emitNullValue() - } -} - -/** - * Toml AST Node for a representation of arrays: key = [value1, value2, value3] - * @property content - */ -public class TomlArray -internal constructor( - override var content: Any, - private val rawContent: String, - lineNo: Int -) : TomlValue(lineNo) { - public constructor( - rawContent: String, - lineNo: Int, - config: TomlConfig = TomlConfig() - ) : this( - rawContent.parse(lineNo, config), - rawContent, - lineNo - ) { - validateQuotes() - } - - /** - * small adaptor to make proper testing of parsing - * - * @param config - * @return converted array to a list - */ - public fun parse(config: TomlConfig = TomlConfig()): List = rawContent.parse(lineNo, config) - - /** - * small validation for quotes: each quote should be closed in a key - */ - private fun validateQuotes() { - if (rawContent.count { it == '\"' } % 2 != 0 || rawContent.count { it == '\'' } % 2 != 0) { - throw ParseException( - "Not able to parse the key: [$rawContent] as it does not have closing quote", - lineNo - ) - } - } - - @Suppress("UNCHECKED_CAST") - public override fun write( - emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean - ) { - emitter.startArray() - - val content = (content as List).map { - if (it is List<*>) { - TomlArray(it, "", 0) - } else { - it as TomlValue - } - } - - val last = content.lastIndex - - if (multiline) { - emitter.indent() - - content.forEachIndexed { i, value -> - emitter.emitNewLine() - .emitIndent() - - value.write(emitter, config, multiline = value is TomlArray) - - if (i < last) { - emitter.emitElementDelimiter() - } - } - - emitter.dedent() - emitter.emitNewLine() - .emitIndent() - } else { - content.forEachIndexed { i, value -> - emitter.emitWhitespace() - - value.write(emitter, config) - - if (i < last) { - emitter.emitElementDelimiter() - } - } - - emitter.emitWhitespace() - } - - emitter.endArray() - } - - public companion object { - /** - * recursively parse TOML array from the string: [ParsingArray -> Trimming values -> Parsing Nested Arrays] - */ - private fun String.parse(lineNo: Int, config: TomlConfig = TomlConfig()): List = - this.parseArray() - .map { it.trim() } - .map { if (it.startsWith("[")) it.parse(lineNo, config) else it.parseValue(lineNo, config) } - - /** - * method for splitting the string to the array: "[[a, b], [c], [d]]" to -> [a,b] [c] [d] - */ - @Suppress("NESTED_BLOCK", "TOO_LONG_FUNCTION") - 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 nbBrackets = 0 - var isInBasicString = false - var isInLiteralString = false - var bufferBetweenCommas = StringBuilder() - val result: MutableList = mutableListOf() - - val trimmed = trimBrackets() - for (i in trimmed.indices) { - when (val current = trimmed[i]) { - '[' -> { - nbBrackets++ - bufferBetweenCommas.append(current) - } - ']' -> { - nbBrackets-- - bufferBetweenCommas.append(current) - } - '\'' -> { - if (!isInBasicString) { - isInLiteralString = !isInLiteralString - } - bufferBetweenCommas.append(current) - } - '"' -> { - if (!isInLiteralString) { - if (!isInBasicString) { - isInBasicString = true - } else if (trimmed[i - 1] != '\\') { - isInBasicString = false - } - } - bufferBetweenCommas.append(current) - } - // split only if we are on the highest level of brackets (all brackets are closed) - // and if we're not in a string - ',' -> if (isInBasicString || isInLiteralString || nbBrackets != 0) { - bufferBetweenCommas.append(current) - } else { - result.add(bufferBetweenCommas.toString()) - bufferBetweenCommas = StringBuilder() - } - else -> bufferBetweenCommas.append(current) - } - } - result.add(bufferBetweenCommas.toString()) - return result - } - } -} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlFile.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlFile.kt new file mode 100644 index 00000000..b1cef672 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlFile.kt @@ -0,0 +1,33 @@ +package com.akuleshov7.ktoml.tree.nodes + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.exceptions.InternalAstException +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * A root node for TOML Abstract Syntax Tree + */ +@Suppress("EMPTY_PRIMARY_CONSTRUCTOR") +public class TomlFile() : TomlNode( + lineNo = 0, + comments = emptyList(), + inlineComment = "" +) { + override val name: String = "rootNode" + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor(config: TomlConfig) : this() + + override fun getNeighbourNodes(): MutableList = + throw InternalAstException("Invalid call to getNeighbourNodes() for TomlFile node") + + override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ): Unit = emitter.writeChildren(children, config) + + override fun toString(): String = "rootNode" +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlNode.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlNode.kt similarity index 69% rename from ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlNode.kt rename to ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlNode.kt index d291ba3e..37074e8f 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlNode.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlNode.kt @@ -2,10 +2,15 @@ * File contains all classes used in Toml AST node */ -package com.akuleshov7.ktoml.tree +package com.akuleshov7.ktoml.tree.nodes +import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig import com.akuleshov7.ktoml.exceptions.InternalAstException +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlValue import com.akuleshov7.ktoml.writers.TomlEmitter public const val EMPTY_TECHNICAL_NODE: String = "technical_node" @@ -15,15 +20,27 @@ public const val EMPTY_TECHNICAL_NODE: String = "technical_node" * Toml specification includes a list of supported data types: * String, Integer, Float, Boolean, Datetime, Array, and Table. * - * @property content - original node content (used for logging and tests only) + * @param comments Comments prepended to the current node + * * @property lineNo - the number of a line from TOML that is linked to the current node - * @property config + * @property inlineComment A comment appended to the end of the line */ public sealed class TomlNode( - public open val content: String, public open val lineNo: Int, - public open val config: TomlConfig = TomlConfig() + comments: List, + public val inlineComment: String ) { + @Deprecated( + message = "content was replaced with toString; will be removed in future releases.", + replaceWith = ReplaceWith("toString()") + ) + @Suppress("CUSTOM_GETTERS_SETTERS") + public val content: String get() = toString() + + /** + * A list of comments prepended to the node. + */ + public val comments: MutableList = comments.toMutableList() public open val children: MutableList = mutableListOf() public open var parent: TomlNode? = null @@ -38,11 +55,13 @@ public sealed class TomlNode( key: TomlKey, value: TomlValue, lineNo: Int, - config: TomlConfig = TomlConfig() + comments: List, + inlineComment: String, + config: TomlInputConfig = TomlInputConfig() ) : this( - "${key.content}=${value.content}", lineNo, - config + comments, + inlineComment ) /** @@ -68,8 +87,10 @@ public sealed class TomlNode( * @throws InternalAstException */ public fun findTableInAstByName(tableName: String): TomlTable? { + val tableKey = TomlKey(tableName, lineNo) + // getting all child-tables (and arrays of tables) that have the same name as we are trying to find - val simpleTable = this.children.filterIsInstance().filter { it.fullTableName == tableName } + val simpleTable = this.children.filterIsInstance().filter { it.fullTableKey == tableKey } // there cannot be more than 1 table node with the same name on the same level in the tree if (simpleTable.size > 1) { throw InternalAstException( @@ -82,7 +103,7 @@ public sealed class TomlNode( .map { it.children } .flatten() .filterIsInstance() - .filter { it.fullTableName == tableName } + .filter { it.fullTableKey == tableKey } .toList() // return the table that we found among the list of child tables or in the array of tables return simpleTable.lastOrNull() ?: tableFromElements.lastOrNull() @@ -133,9 +154,19 @@ public sealed class TomlNode( } else { // creating a synthetic (technical) fragment of the table val newChildTableName = if (tomlTable is TomlArrayOfTables) { - TomlArrayOfTables("[[$subTable]]", lineNo, config, true) + TomlArrayOfTables( + TomlKey(subTable, lineNo), + lineNo, + true + ) } else { - TomlTablePrimitive("[$subTable]", lineNo, config, true) + TomlTablePrimitive( + TomlKey(subTable, lineNo), + lineNo, + tomlTable.comments, + tomlTable.inlineComment, + true + ) } previousParent.determineParentAndInsertFragmentOfTable(newChildTableName) newChildTableName @@ -200,6 +231,19 @@ public sealed class TomlNode( } } + @Deprecated( + message = "TomlConfig is deprecated; use TomlOutputConfig instead. Will be removed in next releases.", + replaceWith = ReplaceWith( + "write(emitter, config, multiline)", + "com.akuleshov7.ktoml.TomlOutputConfig" + ) + ) + public fun write( + emitter: TomlEmitter, + config: TomlConfig, + multiline: Boolean = false + ): Unit = write(emitter, config.output, multiline) + /** * Writes this node as text to [emitter]. * @@ -207,36 +251,81 @@ public sealed class TomlNode( * @param config The [TomlConfig] instance. Defaults to the node's config. * @param multiline Whether to write the node over multiple lines, if possible. */ - public abstract fun write( + @Deprecated( + message = "The multiline parameter overload is deprecated, use the multiline" + + " property on supported TomlValue types instead. Will be removed in next releases.", + replaceWith = ReplaceWith("write(emitter, config)") + ) + public fun write( emitter: TomlEmitter, - config: TomlConfig = this.config, + config: TomlOutputConfig = TomlOutputConfig(), multiline: Boolean = false + ): Unit = write(emitter, config) + + /** + * Writes this node as text to [emitter]. + * + * @param emitter The [TomlEmitter] instance to write to. + * @param config The [TomlConfig] instance. Defaults to the node's config. + */ + public abstract fun write( + emitter: TomlEmitter, + config: TomlOutputConfig = TomlOutputConfig() ) protected open fun TomlEmitter.writeChildren( children: List, - config: TomlConfig, - multiline: Boolean + config: TomlOutputConfig ) { val last = children.lastIndex children.forEachIndexed { i, child -> - emitIndent() - child.write(emitter = this, config, multiline) + writeChildComments(child) + + // Prevent double indentation on dotted table elements. + if (child !is TomlTable || (!child.isSynthetic && child.getFirstChild() !is TomlTable)) { + emitIndent() + } + + child.write(emitter = this, config) + + if (child is TomlKeyValue || child is TomlInlineTable) { + writeChildInlineComment(child) + } if (i < last) { emitNewLine() - // Primitive pairs have a single newline after, except when a table - // follows. - if (child !is TomlKeyValuePrimitive || - children[i + 1] is TomlTable) { + // A single newline follows single-line pairs, except when a table + // follows. Two newlines follow multi-line pairs. + if ((child is TomlKeyValueArray && child.isMultiline()) || children[i + 1] is TomlTable) { emitNewLine() } } } } + protected fun TomlEmitter.writeChildComments(child: TomlNode) { + child.comments.forEach { comment -> + emitIndent() + .emitComment(comment) + .emitNewLine() + } + } + + protected fun TomlEmitter.writeChildInlineComment(child: TomlNode) { + if (child.inlineComment.isNotEmpty()) { + emitComment(child.inlineComment, inline = true) + } + } + + // Todo: Do we keep whitespace in pairs and change parser tests? Trim it and + // maintain compatibility? Add a "formatting" option later? + override fun toString(): String = + Toml.tomlWriter + .writeNode(this) + .replace(" = ", "=") + public companion object { // number of spaces that is used to indent levels internal const val INDENTING_LEVEL = 4 @@ -254,7 +343,7 @@ public sealed class TomlNode( level: Int = 0 ) { val spaces = " ".repeat(INDENTING_LEVEL * level) - result.append("$spaces - ${node::class.simpleName} (${node.content})\n") + result.append("$spaces - ${node::class.simpleName} ($node)\n") node.children.forEach { child -> prettyPrint(child, result, level + 1) } diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlStubEmptyNode.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/other/TomlStubEmptyNode.kt similarity index 51% rename from ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlStubEmptyNode.kt rename to ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/other/TomlStubEmptyNode.kt index b30910fd..d2820ca2 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlStubEmptyNode.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/other/TomlStubEmptyNode.kt @@ -1,6 +1,7 @@ -package com.akuleshov7.ktoml.tree +package com.akuleshov7.ktoml.tree.nodes import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlOutputConfig import com.akuleshov7.ktoml.writers.TomlEmitter /** @@ -9,18 +10,24 @@ import com.akuleshov7.ktoml.writers.TomlEmitter * * Instances of this stub will be added as children to such parsed tables */ -public class TomlStubEmptyNode(lineNo: Int, config: TomlConfig = TomlConfig()) : TomlNode( - EMPTY_TECHNICAL_NODE, +public class TomlStubEmptyNode(lineNo: Int) : TomlNode( lineNo, - config + comments = emptyList(), + inlineComment = "" ) { override val name: String = EMPTY_TECHNICAL_NODE + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor(lineNo: Int, config: TomlConfig) : this(lineNo) + override fun write( emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean + config: TomlOutputConfig ) { // Nothing to write in stub nodes. } + + override fun toString(): String = EMPTY_TECHNICAL_NODE } diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValue.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValue.kt similarity index 66% rename from ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValue.kt rename to ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValue.kt index f1d37e83..7c671c0b 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValue.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValue.kt @@ -1,8 +1,11 @@ -package com.akuleshov7.ktoml.tree +package com.akuleshov7.ktoml.tree.nodes -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig import com.akuleshov7.ktoml.exceptions.ParseException -import com.akuleshov7.ktoml.parsers.findBeginningOfTheComment +import com.akuleshov7.ktoml.parsers.takeBeforeComment +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey +import com.akuleshov7.ktoml.tree.nodes.pairs.values.* import com.akuleshov7.ktoml.writers.TomlEmitter private typealias ValueCreator = (String, Int) -> TomlValue @@ -14,6 +17,17 @@ internal interface TomlKeyValue { var key: TomlKey val value: TomlValue val lineNo: Int + val comments: List + val inlineComment: String + + @Deprecated( + message = "The config parameter was removed. Will be removed in future releases.", + replaceWith = ReplaceWith("createTomlTableFromDottedKey(parentNode)") + ) + fun createTomlTableFromDottedKey( + parentNode: TomlNode, + config: TomlInputConfig = TomlInputConfig() + ): TomlTablePrimitive = createTomlTableFromDottedKey(parentNode) /** * this is a small hack to support dotted keys @@ -23,37 +37,38 @@ internal interface TomlKeyValue { * and we will let our Table mechanism to do everything for us * * @param parentNode - * @param config * @return the table that is parsed from a dotted key */ - fun createTomlTableFromDottedKey(parentNode: TomlNode, config: TomlConfig = TomlConfig()): TomlTablePrimitive { + fun createTomlTableFromDottedKey(parentNode: TomlNode): TomlTablePrimitive { // for a key: a.b.c it will be [a, b] val syntheticTablePrefix = this.key.keyParts.dropLast(1) // creating new key with the last dot-separated fragment - val realKeyWithoutDottedPrefix = TomlKey(key.content, lineNo) + val realKeyWithoutDottedPrefix = TomlKey(key.last(), lineNo) // updating current KeyValue with this key this.key = realKeyWithoutDottedPrefix // tables should contain fully qualified name, so we need to add parental name - val parentalPrefix = if (parentNode is TomlTable) "${parentNode.fullTableName}." else "" + val parentalPrefix = if (parentNode is TomlTable) parentNode.fullTableKey.keyParts else emptyList() // and creating a new table that will be created from dotted key return TomlTablePrimitive( - "[$parentalPrefix${syntheticTablePrefix.joinToString(".")}]", + TomlKey(parentalPrefix + syntheticTablePrefix), lineNo, - config, + comments, + inlineComment, true ) } + fun isMultiline() = false + fun write( emitter: TomlEmitter, - config: TomlConfig, - multiline: Boolean + config: TomlOutputConfig ) { - key.write(emitter, config) + key.write(emitter) emitter.emitPairDelimiter() - value.write(emitter, config, multiline) + value.write(emitter, config) } } @@ -65,7 +80,7 @@ internal interface TomlKeyValue { * @param config * @return an object of type Array that was parsed from string */ -public fun String.parseList(lineNo: Int, config: TomlConfig): TomlArray = TomlArray(this, lineNo, config) +public fun String.parseList(lineNo: Int, config: TomlInputConfig): TomlArray = TomlArray(this, lineNo, config) /** * parse and split a string in a key-value format @@ -75,20 +90,11 @@ public fun String.parseList(lineNo: Int, config: TomlConfig): TomlArray = TomlAr * @return a resulted key-value pair * @throws ParseException */ -public fun String.splitKeyValue(lineNo: Int, config: TomlConfig = TomlConfig()): Pair { - // finding the index of the last quote, if no quotes are found, then use the length of the string - val closingQuoteIndex = listOf( - this.lastIndexOf("\""), - this.lastIndexOf("\'"), - this.lastIndexOf("\"\"\"") - ).filterNot { it == -1 }.maxOrNull() ?: 0 - - // finding the index of a commented part of the string - // search starts goes from the closingQuoteIndex to the end of the string - val firstHash = this.findBeginningOfTheComment(closingQuoteIndex) +public fun String.splitKeyValue(lineNo: Int, config: TomlInputConfig = TomlInputConfig()): Pair { + val pair = takeBeforeComment(config.allowEscapedQuotesInLiteralStrings) // searching for an equals sign that should be placed main part of the string (not in the comment) - val firstEqualsSign = this.substring(0, firstHash).indexOfFirst { it == '=' } + val firstEqualsSign = pair.indexOfFirst { it == '=' } // equals sign not found in the string if (firstEqualsSign == -1) { @@ -101,8 +107,8 @@ public fun String.splitKeyValue(lineNo: Int, config: TomlConfig = TomlConfig()): } // k = v # comment -> key is `k`, value is `v` - val key = this.substring(0, firstEqualsSign).trim() - val value = this.substring(firstEqualsSign + 1, firstHash).trim() + val key = pair.substring(0, firstEqualsSign).trim() + val value = pair.substring(firstEqualsSign + 1).trim() return Pair( key.checkNotEmpty("key", this, config, lineNo), @@ -118,7 +124,11 @@ public fun String.splitKeyValue(lineNo: Int, config: TomlConfig = TomlConfig()): * @param config * @return parsed TomlNode value */ -public fun String.parseValue(lineNo: Int, config: TomlConfig): TomlValue = when (this) { +public fun String.parseValue(lineNo: Int, config: TomlInputConfig): TomlValue = when (this) { + // ===== special values + "+inf", "inf" -> TomlDouble(Double.POSITIVE_INFINITY) + "-inf" -> TomlDouble(Double.NEGATIVE_INFINITY) + "-nan", "+nan", "nan", "-NaN", "+NaN", "NaN" -> TomlDouble(Double.NaN) // ===== null values "null", "nil", "NULL", "NIL", "" -> if (config.allowNullValues) { TomlNull(lineNo) @@ -127,13 +137,9 @@ public fun String.parseValue(lineNo: Int, config: TomlConfig): TomlValue = when } // ===== boolean vales "true", "false" -> TomlBoolean(this, lineNo) - else -> when (this[0]) { - // ===== literal strings - '\'' -> if (this.startsWith("'''")) { - TomlBasicString(this, lineNo) - } else { - TomlLiteralString(this, lineNo, config) - } + // ===== strings + else -> when (this.first()) { + '\'' -> TomlLiteralString(this, lineNo, config) // ===== basic strings '\"' -> TomlBasicString(this, lineNo) else -> tryParseValue(lineNo, ::TomlLong) // ==== integer values @@ -158,7 +164,7 @@ private inline fun String.tryParseValue( private fun String.checkNotEmpty( log: String, content: String, - config: TomlConfig = TomlConfig(), + config: TomlInputConfig = TomlInputConfig(), lineNo: Int ): String = this.also { diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValueArray.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValueArray.kt new file mode 100644 index 00000000..ca36c5d4 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValueArray.kt @@ -0,0 +1,87 @@ +package com.akuleshov7.ktoml.tree.nodes + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlArray +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlValue +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * Class for parsing and storing Array of Tables in AST. + * @property lineNo + * @property key + * @property value + */ +public class TomlKeyValueArray( + override var key: TomlKey, + override val value: TomlValue, + override val lineNo: Int, + comments: List, + inlineComment: String +) : TomlNode( + lineNo, + comments, + inlineComment +), TomlKeyValue { + override val name: String = key.last() + + // adaptor for a string pair of key-value + public constructor( + keyValuePair: Pair, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "", + config: TomlInputConfig = TomlInputConfig() + ) : this( + TomlKey(keyValuePair.first, lineNo), + keyValuePair.second.parseList(lineNo, config), + lineNo, + comments, + inlineComment + ) + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + key: TomlKey, + value: TomlValue, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "", + name: String, + config: TomlConfig + ) : this( + key, + value, + lineNo, + comments, + inlineComment + ) + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + keyValuePair: Pair, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "", + config: TomlConfig + ) : this( + keyValuePair, + lineNo, + comments, + inlineComment, + config.input + ) + + override fun isMultiline(): Boolean = (value as TomlArray).multiline + + public override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ): Unit = super.write(emitter, config) +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValuePrimitive.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValuePrimitive.kt new file mode 100644 index 00000000..12d8ebb6 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValuePrimitive.kt @@ -0,0 +1,83 @@ +package com.akuleshov7.ktoml.tree.nodes + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlValue +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * class for parsing and storing simple single value types in AST + * @property lineNo + * @property key + * @property value + */ +public class TomlKeyValuePrimitive( + override var key: TomlKey, + override val value: TomlValue, + override val lineNo: Int, + comments: List, + inlineComment: String +) : TomlNode( + lineNo, + comments, + inlineComment +), TomlKeyValue { + override val name: String = key.last() + + // adaptor for a string pair of key-value + public constructor( + keyValuePair: Pair, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "", + config: TomlInputConfig = TomlInputConfig() + ) : this( + TomlKey(keyValuePair.first, lineNo), + keyValuePair.second.parseValue(lineNo, config), + lineNo, + comments, + inlineComment + ) + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + key: TomlKey, + value: TomlValue, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "", + config: TomlConfig + ) : this( + key, + value, + lineNo, + comments, + inlineComment + ) + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + keyValuePair: Pair, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "", + config: TomlConfig + ) : this( + keyValuePair, + lineNo, + comments, + inlineComment, + config.input + ) + + public override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ): Unit = super.write(emitter, config) +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/keys/TomlKey.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/keys/TomlKey.kt new file mode 100644 index 00000000..eead3188 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/keys/TomlKey.kt @@ -0,0 +1,98 @@ +package com.akuleshov7.ktoml.tree.nodes.pairs.keys + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.exceptions.TomlWritingException +import com.akuleshov7.ktoml.parsers.splitKeyToTokens +import com.akuleshov7.ktoml.parsers.trimQuotes +import com.akuleshov7.ktoml.writers.TomlEmitter +import com.akuleshov7.ktoml.writers.TomlStringEmitter + +/** + * Class that represents a toml key-value pair. + * Key has TomlKey type, Value has TomlValue type + * + * @property keyParts The parts of the key, separated by dots. + */ +public class TomlKey internal constructor( + internal val keyParts: List +) { + /** + * Whether the key has multiple dot-separated parts. + */ + internal val isDotted: Boolean = keyParts.size > 1 + + @Deprecated( + message = "rawContent is deprecated; use toString for a lazily formatted version. Will be removed in future releases.", + replaceWith = ReplaceWith("toString()") + ) + @Suppress("CUSTOM_GETTERS_SETTERS") + public val rawContent: String get() = toString() + + @Deprecated( + message = "content was replaced with last. Will be removed in future releases.", + replaceWith = ReplaceWith("last()") + ) + @Suppress("CUSTOM_GETTERS_SETTERS") + public val content: String get() = last() + + /** + * @param rawContent + * @param lineNo + */ + public constructor( + rawContent: String, + lineNo: Int + ) : this(rawContent.splitKeyToTokens(lineNo)) + + /** + * Gets the last key part, with all whitespace and quotes trimmed, i.e. `c` in + * `a.b.' c '` + */ + public fun last(): String = keyParts.last().trimQuotes().trim() + + @Deprecated( + message = "TomlConfig is deprecated. Will be removed in next releases.", + replaceWith = ReplaceWith("write(emitter)") + ) + public fun write(emitter: TomlEmitter, config: TomlConfig): Unit = write(emitter) + + public fun write(emitter: TomlEmitter) { + val keys = keyParts + + if (keys.isEmpty() || keys.any(String::isEmpty)) { + throw TomlWritingException( + "Empty keys are not allowed: key is empty or has an empty key part." + ) + } + + keys.forEachIndexed { i, value -> + if (i > 0) { + emitter.emitKeyDot() + } + + when { + value.startsWith('"') && value.endsWith('"') -> + emitter.emitQuotedKey(value.substring(1 until value.lastIndex)) + value.startsWith('\'') && value.endsWith('\'') -> + emitter.emitQuotedKey(value.substring(1 until value.lastIndex), isLiteral = true) + else -> emitter.emitBareKey(value) + } + } + } + + override fun toString(): String = buildString { + val emitter = TomlStringEmitter(this, TomlOutputConfig()) + + write(emitter) + } + + override fun equals(other: Any?): Boolean = when { + this === other -> true + other !is TomlKey -> false + keyParts != other.keyParts -> false + else -> true + } + + override fun hashCode(): Int = keyParts.hashCode() +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlArray.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlArray.kt new file mode 100644 index 00000000..4d87c1f6 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlArray.kt @@ -0,0 +1,191 @@ +package com.akuleshov7.ktoml.tree.nodes.pairs.values + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.exceptions.ParseException +import com.akuleshov7.ktoml.parsers.removeTrailingComma +import com.akuleshov7.ktoml.parsers.trimBrackets +import com.akuleshov7.ktoml.tree.nodes.parseValue +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * Toml AST Node for a representation of arrays: key = [value1, value2, value3] + * @property content + * @property multiline + */ +public class TomlArray internal constructor( + override var content: Any, + public var multiline: Boolean = false +) : TomlValue() { + public constructor( + rawContent: String, + lineNo: Int, + config: TomlInputConfig + ) : this(rawContent.parse(lineNo, config)) { + validateQuotes(rawContent, lineNo) + } + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + rawContent: String, + lineNo: Int, + config: TomlConfig + ) : this(rawContent) + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases.", + replaceWith = ReplaceWith( + "parse(config)", + "com.akuleshov7.ktoml.TomlInputConfig" + ) + ) + public fun parse(config: TomlConfig): List = parse(config.input) + + /** + * small adaptor to make proper testing of parsing + * + * @param config + * @return converted array to a list + */ + @Deprecated( + message = "parse(TomlInputConfig) is deprecated. Will be removed in next releases.", + replaceWith = ReplaceWith("content as List"), + + ) + @Suppress("UNCHECKED_CAST") + public fun parse(config: TomlInputConfig = TomlInputConfig()): List = content as List + + @Suppress("UNCHECKED_CAST") + public override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ) { + emitter.startArray() + + val content = (content as List).map { + if (it is List<*>) { + TomlArray(it, multiline) + } else { + it as TomlValue + } + } + + val last = content.lastIndex + + if (multiline) { + emitter.indent() + + content.forEachIndexed { i, value -> + emitter.emitNewLine() + .emitIndent() + + value.write(emitter, config) + + if (i < last) { + emitter.emitElementDelimiter() + } + } + + emitter.dedent() + emitter.emitNewLine() + .emitIndent() + } else { + content.forEachIndexed { i, value -> + emitter.emitWhitespace() + + value.write(emitter, config) + + if (i < last) { + emitter.emitElementDelimiter() + } + } + + emitter.emitWhitespace() + } + + emitter.endArray() + } + + public companion object { + /** + * recursively parse TOML array from the string: [ParsingArray -> Trimming values -> Parsing Nested Arrays] + */ + private fun String.parse(lineNo: Int, config: TomlInputConfig = TomlInputConfig()): List = + this.parseArray() + .map { it.trim() } + .map { if (it.startsWith("[")) it.parse(lineNo, config) else it.parseValue(lineNo, config) } + + /** + * method for splitting the string to the array: "[[a, b], [c], [d]]" to -> [a,b] [c] [d] + */ + @Suppress("NESTED_BLOCK", "TOO_LONG_FUNCTION") + private fun String.parseArray(): MutableList { + val trimmed = trimBrackets().trim().removeTrailingComma() + // covering cases when the array is intentionally blank: myArray = []. It should be empty and not contain null + if (trimmed.isBlank()) { + return mutableListOf() + } + + var nbBrackets = 0 + var isInBasicString = false + var isInLiteralString = false + var bufferBetweenCommas = StringBuilder() + val result: MutableList = mutableListOf() + + for (i in trimmed.indices) { + when (val current = trimmed[i]) { + '[' -> { + nbBrackets++ + bufferBetweenCommas.append(current) + } + ']' -> { + nbBrackets-- + bufferBetweenCommas.append(current) + } + '\'' -> { + if (!isInBasicString) { + isInLiteralString = !isInLiteralString + } + bufferBetweenCommas.append(current) + } + '"' -> { + if (!isInLiteralString) { + if (!isInBasicString) { + isInBasicString = true + } else if (trimmed[i - 1] != '\\') { + isInBasicString = false + } + } + bufferBetweenCommas.append(current) + } + // split only if we are on the highest level of brackets (all brackets are closed) + // and if we're not in a string + ',' -> if (isInBasicString || isInLiteralString || nbBrackets != 0) { + bufferBetweenCommas.append(current) + } else { + result.add(bufferBetweenCommas.toString()) + bufferBetweenCommas = StringBuilder() + } + else -> bufferBetweenCommas.append(current) + } + } + result.add(bufferBetweenCommas.toString()) + return result + } + + /** + * small validation for quotes: each quote should be closed in a key + */ + private fun validateQuotes(rawContent: String, lineNo: Int) { + if (rawContent.count { it == '\"' } % 2 != 0 || rawContent.count { it == '\'' } % 2 != 0) { + throw ParseException( + "Not able to parse the key: [$rawContent] as it does not have closing quote", + lineNo + ) + } + } + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBasicString.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBasicString.kt new file mode 100644 index 00000000..1d46beff --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBasicString.kt @@ -0,0 +1,92 @@ +package com.akuleshov7.ktoml.tree.nodes.pairs.values + +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.exceptions.ParseException +import com.akuleshov7.ktoml.parsers.convertLineEndingBackslash +import com.akuleshov7.ktoml.parsers.getCountOfOccurrencesOfSubstring +import com.akuleshov7.ktoml.parsers.trimMultilineQuotes +import com.akuleshov7.ktoml.parsers.trimQuotes +import com.akuleshov7.ktoml.utils.convertSpecialCharacters +import com.akuleshov7.ktoml.utils.escapeSpecialCharacters +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * Toml AST Node for a representation of string values: key = "value" (always should have quotes due to TOML standard) + * @property content + * @property multiline Whether the string is multiline. + */ +public class TomlBasicString internal constructor( + override var content: Any, + public var multiline: Boolean = false +) : TomlValue() { + public constructor( + content: String, + lineNo: Int + ) : this(content.verifyAndTrimQuotes(lineNo)) + + override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ) { + val content = content as String + + emitter.emitValue( + content.escapeSpecialCharacters(multiline), + isLiteral = false, + multiline + ) + } + + public companion object { + private fun String.verifyAndTrimQuotes(lineNo: Int): Any = + when { + // ====== multiline string (""") ======= + startsWith("\"\"\"") && endsWith("\"\"\"") -> + trimMultilineQuotes() + .checkCountOfOtherUnescapedQuotes(lineNo) + .convertLineEndingBackslash() + .convertSpecialCharacters(lineNo) + + // ========= basic string ("abc") ======= + startsWith("\"") && endsWith("\"") -> + trimQuotes() + .checkOtherQuotesAreEscaped(lineNo) + .convertSpecialCharacters(lineNo) + + // ============= other =========== + else -> + throw ParseException( + "According to the TOML specification string values (even Enums)" + + " should be wrapped (start and end) with quotes (\"\")," + + " but the following value was not: <$this>.", + lineNo + ) + } + + private fun String.checkOtherQuotesAreEscaped(lineNo: Int): String { + this.forEachIndexed { index, ch -> + if (ch == '\"' && (index == 0 || this[index - 1] != '\\')) { + throw ParseException( + "Found invalid quote that is not escaped." + + " Please remove the quote or use escaping" + + " in <$this> at position = [$index].", lineNo + ) + } + } + return this + } + + private fun String.checkCountOfOtherUnescapedQuotes(lineNo: Int): String { + // Here we do replace because the following is valid: a = """ \""" """ + // We have 1 escaped quote + 2 unescaped quotes + if (this.replace("\\\"", " ").getCountOfOccurrencesOfSubstring("\"\"\"") != 0) { + throw ParseException( + "Multi-line basic string cannot contain 3 or more quotes (\") in a row." + + " Please remove the quotes or use escaping in <$this>", + lineNo + ) + } + return this + } + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBoolean.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBoolean.kt new file mode 100644 index 00000000..c5e39a25 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBoolean.kt @@ -0,0 +1,21 @@ +package com.akuleshov7.ktoml.tree.nodes.pairs.values + +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * Toml AST Node for a representation of boolean types: key = true | false + * @property content + */ +public class TomlBoolean internal constructor( + override var content: Any +) : TomlValue() { + public constructor(content: String, lineNo: Int) : this(content.toBoolean()) + + override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ) { + emitter.emitValue(content as Boolean) + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDateTime.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDateTime.kt new file mode 100644 index 00000000..bc706865 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDateTime.kt @@ -0,0 +1,54 @@ +package com.akuleshov7.ktoml.tree.nodes.pairs.values + +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.exceptions.TomlWritingException +import com.akuleshov7.ktoml.writers.TomlEmitter +import kotlinx.datetime.* + +/** + * Toml AST Node for a representation of date-time types (offset date-time, local date-time, local date, local time) + * @property content + */ +public class TomlDateTime +internal constructor( + override var content: Any +) : TomlValue() { + public constructor(content: String, lineNo: Int) : this(content.trim().parseToDateTime()) + + override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ) { + when (val content = content) { + is Instant -> emitter.emitValue(content) + is LocalDateTime -> emitter.emitValue(content) + is LocalDate -> emitter.emitValue(content) + is LocalTime -> emitter.emitValue(content) + else -> + throw TomlWritingException( + "Unknown date type ${content::class.simpleName}" + ) + } + } + + public companion object { + private fun String.parseToDateTime(): Any = try { + // Offset date-time + // TOML spec allows a space instead of the T, try replacing the first space by a T + replaceFirst(' ', 'T').toInstant() + } catch (e: IllegalArgumentException) { + try { + // Local date-time + toLocalDateTime() + } catch (e: IllegalArgumentException) { + try { + // Local date + toLocalDate() + } catch (e: IllegalArgumentException) { + // Local time + toLocalTime() + } + } + } + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDouble.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDouble.kt new file mode 100644 index 00000000..ea383f84 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDouble.kt @@ -0,0 +1,26 @@ +package com.akuleshov7.ktoml.tree.nodes.pairs.values + +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * 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( + override var content: Any +) : TomlValue() { + public constructor(content: String, lineNo: Int) : this(content.toDouble()) + + public constructor(content: Double, lineNo: Int) : this(content) + + override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ) { + emitter.emitValue(content as Double) + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlLiteralString.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlLiteralString.kt new file mode 100644 index 00000000..1624a6ef --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlLiteralString.kt @@ -0,0 +1,140 @@ +package com.akuleshov7.ktoml.tree.nodes.pairs.values + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.exceptions.ParseException +import com.akuleshov7.ktoml.exceptions.TomlWritingException +import com.akuleshov7.ktoml.parsers.convertLineEndingBackslash +import com.akuleshov7.ktoml.parsers.getCountOfOccurrencesOfSubstring +import com.akuleshov7.ktoml.parsers.trimMultilineLiteralQuotes +import com.akuleshov7.ktoml.parsers.trimSingleQuotes +import com.akuleshov7.ktoml.utils.isControlChar +import com.akuleshov7.ktoml.utils.isMultilineControlChar +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * 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 + * @property multiline Whether the string is multiline. + */ +public class TomlLiteralString internal constructor( + override var content: Any, + public var multiline: Boolean = false +) : TomlValue() { + public constructor( + content: String, + lineNo: Int, + config: TomlInputConfig = TomlInputConfig() + ) : this(content.verifyAndTrimQuotes(lineNo, config)) + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + content: String, + lineNo: Int, + config: TomlConfig + ) : this( + content, + lineNo, + config.input + ) + + override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ) { + val content = content as String + + emitter.emitValue( + content.escapeQuotesAndVerify(config, multiline), + isLiteral = true, + multiline + ) + } + + public companion object { + private fun String.verifyAndTrimQuotes(lineNo: Int, config: TomlInputConfig): Any = + if (startsWith("'''") && endsWith("'''")) { + val contentString = trimMultilineLiteralQuotes() + .checkCountOfOtherQuotes(lineNo) + val rawContent = if (config.allowEscapedQuotesInLiteralStrings) { + contentString.convertSingleQuotes() + } else { + contentString + } + + rawContent.convertLineEndingBackslash() + } else if (startsWith("'") && endsWith("'")) { + val contentString = trimSingleQuotes() + if (config.allowEscapedQuotesInLiteralStrings) contentString.convertSingleQuotes() else contentString + } else { + throw ParseException( + "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("\\'", "'") + + private fun String.escapeQuotesAndVerify(config: TomlOutputConfig, multiline: Boolean) = + when { + multiline -> + when { + any(Char::isMultilineControlChar) -> + throw TomlWritingException( + "Control characters (excluding tab and line" + + " terminators) are not permitted in" + + " multiline literal strings." + ) + config.allowEscapedQuotesInLiteralStrings -> + replace("'''", "''\\'") + "'''" in this -> + throw TomlWritingException( + "Three or more consecutive single quotes are not" + + " permitted in multiline literal strings." + ) + else -> this + } + any(Char::isControlChar) -> + throw TomlWritingException( + "Control characters (excluding tab) are not permitted" + + " in literal strings." + ) + '\'' in this -> + if (config.allowEscapedQuotesInLiteralStrings) { + replace("'", "\\'") + } else { + throw TomlWritingException( + "Single quotes are not permitted in literal string" + + " by default. Set allowEscapedQuotesInLiteral" + + "Strings to true in the config to ignore this." + ) + } + else -> this + } + + private fun String.checkCountOfOtherQuotes(lineNo: Int): String { + if (this.replace("\\'", " ").getCountOfOccurrencesOfSubstring("'''") != 0) { + throw ParseException( + "Multi-line literal basic string cannot contain 3 or more quotes (') in a row." + + " Please remove the quotes or set allowEscapedQuotesInLiteral " + + "Strings to true in the config and add escaping<$this>", + lineNo + ) + } + return this + } + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlLong.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlLong.kt new file mode 100644 index 00000000..62ed350c --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlLong.kt @@ -0,0 +1,53 @@ +package com.akuleshov7.ktoml.tree.nodes.pairs.values + +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.writers.IntegerRepresentation +import com.akuleshov7.ktoml.writers.IntegerRepresentation.* +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * Toml AST Node for a representation of Arbitrary 64-bit signed integers: key = 1 + * @property content + * @property representation The representation of the integer. + */ +public class TomlLong internal constructor( + override var content: Any, + public var representation: IntegerRepresentation = DECIMAL +) : TomlValue() { + public constructor(content: String, lineNo: Int) : this(content.parse()) + + private constructor(pair: Pair) : this(pair.first, pair.second) + + override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ) { + emitter.emitValue(content as Long, representation) + } + + public companion object { + private const val BIN_RADIX = 2 + private const val HEX_RADIX = 16 + private const val OCT_RADIX = 8 + private val prefixRegex = "(?<=0[box])".toRegex() + + private fun String.parse(): Pair { + val value = replace("_", "").split(prefixRegex, limit = 2) + + return if (value.size == 2) { + val (prefix, digits) = value + + when (prefix) { + "0b" -> digits.toLong(BIN_RADIX) to BINARY + "0o" -> digits.toLong(OCT_RADIX) to OCTAL + "0x" -> digits.toLong(HEX_RADIX) to HEX + else -> throw NumberFormatException( + "Invalid radix prefix $prefix: expected \"0b\", \"0o\", or \"0x\"." + ) + } + } else { + value.first().toLong() to DECIMAL + } + } + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlNull.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlNull.kt new file mode 100644 index 00000000..03f7c479 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlNull.kt @@ -0,0 +1,25 @@ +package com.akuleshov7.ktoml.tree.nodes.pairs.values + +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * Toml AST Node for a representation of null: + * null, nil, NULL, NIL or empty (key = ) + */ +@Suppress("EMPTY_PRIMARY_CONSTRUCTOR") // Will be corrected after removal of the deprecated constructor. +public class TomlNull() : TomlValue() { + override var content: Any = "null" + + @Deprecated( + message = "lineNo parameter is deprecated, should be removed. Will be removed in next releases." + ) + public constructor(lineNo: Int) : this() + + override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ) { + emitter.emitNullValue() + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlValue.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlValue.kt new file mode 100644 index 00000000..a8827c38 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlValue.kt @@ -0,0 +1,53 @@ +/** + * All representations of TOML value nodes are stored in this file + */ + +package com.akuleshov7.ktoml.tree.nodes.pairs.values + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * Base class for all nodes that represent values + */ +public sealed class TomlValue { + @Deprecated(message = "lineNo is deprecated. Will be removed in next releases.") + public val lineNo: Int = 0 + public abstract var content: Any + + @Deprecated( + message = "TomlConfig is deprecated; use TomlOutputConfig instead. Will be removed in next releases.", + replaceWith = ReplaceWith( + "write(emitter, config, multiline)", + "com.akuleshov7.ktoml.TomlOutputConfig" + ) + ) + public fun write( + emitter: TomlEmitter, + config: TomlConfig, + multiline: Boolean = false + ): Unit = write(emitter, config.output, multiline) + + @Deprecated( + message = "The multiline parameter overload is deprecated, use the multiline" + + " property on supported types instead. Will be removed in next releases.", + replaceWith = ReplaceWith("write(emitter, config)") + ) + public fun write( + emitter: TomlEmitter, + config: TomlOutputConfig = TomlOutputConfig(), + multiline: Boolean + ): Unit = write(emitter, config) + + /** + * Writes this value to the specified [emitter]. + * + * @param emitter + * @param config + */ + public abstract fun write( + emitter: TomlEmitter, + config: TomlOutputConfig = TomlOutputConfig() + ) +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlArrayOfTables.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlArrayOfTables.kt new file mode 100644 index 00000000..42184c4a --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlArrayOfTables.kt @@ -0,0 +1,125 @@ +/** + * Array of tables https://toml.io/en/v1.0.0#array-of-tables + */ + +package com.akuleshov7.ktoml.tree.nodes + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * Class representing array of tables + * + * @throws ParseException if the content is wrong + */ +// FixMe: this class is mostly identical to the TomlTable - we should unify them together +public class TomlArrayOfTables( + fullTableKey: TomlKey, + lineNo: Int, + isSynthetic: Boolean = false +) : TomlTable( + fullTableKey, + lineNo, + comments = emptyList(), + inlineComment = "", + isSynthetic +) { + public override val type: TableType = TableType.ARRAY + + public constructor( + content: String, + lineNo: Int, + isSynthetic: Boolean = false + ) : this( + parseSection(content, lineNo, isArray = true), + lineNo, + isSynthetic + ) + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + content: String, + lineNo: Int, + config: TomlConfig, + isSynthetic: Boolean = false + ) : this( + content, + lineNo, + isSynthetic + ) + + override fun TomlEmitter.writeHeader(config: TomlOutputConfig) { + startTableArrayHeader() + + fullTableKey.write(emitter = this) + + endTableArrayHeader() + } + + override fun TomlEmitter.writeChildren( + children: List, + config: TomlOutputConfig + ) { + val last = children.lastIndex + + children.forEachIndexed { i, child -> + if (child is TomlArrayOfTablesElement) { + if (parent !is TomlArrayOfTablesElement) { + emitIndent() + } + + writeChildComments(child) + writeHeader(config) + writeChildInlineComment(child) + + if (!child.hasNoChildren()) { + emitNewLine() + } + + indent() + + child.write(emitter = this, config) + + dedent() + + if (i < last) { + emitNewLine() + + // Primitive pairs have a single newline after, except when a + // table follows. + if (child !is TomlKeyValuePrimitive || children[i + 1] is TomlTable) { + emitNewLine() + } + } + } else { + child.write(emitter = this, config) + } + } + } +} + +/** + * This class is used to store elements of array of tables (bucket for key-value records) + */ +public class TomlArrayOfTablesElement( + lineNo: Int, + comments: List, + inlineComment: String +) : TomlNode( + lineNo, + comments, + inlineComment +) { + override val name: String = EMPTY_TECHNICAL_NODE + + override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ): Unit = emitter.writeChildren(children, config) + + override fun toString(): String = EMPTY_TECHNICAL_NODE +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlInlineTable.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlInlineTable.kt new file mode 100644 index 00000000..fa348412 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlInlineTable.kt @@ -0,0 +1,145 @@ +package com.akuleshov7.ktoml.tree.nodes + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.exceptions.ParseException +import com.akuleshov7.ktoml.parsers.parseTomlKeyValue +import com.akuleshov7.ktoml.parsers.trimCurlyBraces +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * Class for parsing and representing of inline tables: inline = { a = 5, b = 6 , c = 7 } + * @property tomlKeyValues The key-value pairs in the inline table + * @property key + */ +public class TomlInlineTable internal constructor( + public val key: TomlKey, + internal val tomlKeyValues: List, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "" +) : TomlNode( + lineNo, + comments, + inlineComment +) { + @Suppress("CUSTOM_GETTERS_SETTERS") + override val name: String get() = key.toString() + + public constructor( + keyValuePair: Pair, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "", + config: TomlInputConfig = TomlInputConfig() + ) : this( + TomlKey(keyValuePair.first, lineNo), + keyValuePair.second.parseInlineTableValue(lineNo, config), + lineNo, + comments, + inlineComment + ) + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + keyValuePair: Pair, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "", + config: TomlConfig + ) : this( + keyValuePair, + lineNo, + comments, + inlineComment, + config.input + ) + + public fun returnTable(tomlFileHead: TomlFile, currentParentalNode: TomlNode): TomlTable { + val tomlTable = TomlTablePrimitive( + TomlKey( + if (currentParentalNode is TomlTable) { + currentParentalNode.fullTableKey.keyParts + name + } else { + listOf(name) + } + ), + lineNo, + comments, + inlineComment + ) + + // FixMe: this code duplication can be unified with the logic in TomlParser + tomlKeyValues.forEach { keyValue -> + when { + keyValue is TomlKeyValue && keyValue.key.isDotted -> { + // in case parser has faced dot-separated complex key (a.b.c) it should create proper table [a.b], + // because table is the same as dotted key + val newTableSection = keyValue.createTomlTableFromDottedKey(tomlTable) + + tomlFileHead + .insertTableToTree(newTableSection) + .appendChild(keyValue) + } + + keyValue is TomlInlineTable -> tomlFileHead.insertTableToTree( + keyValue.returnTable(tomlFileHead, tomlTable) + ) + + // otherwise, it should simply append the keyValue to the parent + else -> tomlTable.appendChild(keyValue) + + } + } + return tomlTable + } + + public override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ) { + key.write(emitter) + + emitter.emitPairDelimiter() + .startInlineTable() + + tomlKeyValues.forEachIndexed { i, pair -> + if (i > 0) { + emitter.emitElementDelimiter() + } + + emitter.emitWhitespace() + + pair.write(emitter, config) + } + + emitter.emitWhitespace() + .endInlineTable() + } + + public companion object { + private fun String.parseInlineTableValue( + lineNo: Int, + config: TomlInputConfig + ): List { + val parsedList = this + .trimCurlyBraces() + .trim() + .also { + if (it.endsWith(",")) { + throw ParseException( + "Trailing commas are not permitted in inline tables: [$this] ", lineNo + ) + } + } + .split(",") + .map { it.parseTomlKeyValue(lineNo, comments = emptyList(), inlineComment = "", config) } + + return parsedList + } + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTable.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTable.kt new file mode 100644 index 00000000..5bc37808 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTable.kt @@ -0,0 +1,176 @@ +/** + * Common class for tables + */ + +package com.akuleshov7.ktoml.tree.nodes + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.exceptions.ParseException +import com.akuleshov7.ktoml.parsers.takeBeforeComment +import com.akuleshov7.ktoml.parsers.trimBrackets +import com.akuleshov7.ktoml.parsers.trimDoubleBrackets +import com.akuleshov7.ktoml.parsers.trimQuotes +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey +import com.akuleshov7.ktoml.writers.TomlEmitter +import com.akuleshov7.ktoml.writers.TomlStringEmitter +import kotlin.jvm.JvmStatic + +/** + * Abstract class to represent all types of tables: primitive/arrays/etc. + * @property lineNo - line number + * @property isSynthetic + * @property fullTableKey + */ +@Suppress("COMMENT_WHITE_SPACE") +public abstract class TomlTable( + public var fullTableKey: TomlKey, + override val lineNo: Int, + comments: List, + inlineComment: String, + public var isSynthetic: Boolean = false +) : TomlNode( + lineNo, + comments, + inlineComment +) { + @Deprecated( + message = "fullTableName was replaced with fullTableKey; will be removed in future releases.", + replaceWith = ReplaceWith("fullTableKey.toString()") + ) + @Suppress("NO_CORRESPONDING_PROPERTY", "CUSTOM_GETTERS_SETTERS") + public var fullTableName: String + get() = fullTableKey.toString() + set(value) { + fullTableKey = TomlKey(value, lineNo) + } + + // list of tables (including sub-tables) that are included in this table (e.g.: {a, a.b, a.b.c} in a.b.c) + public var tablesList: List = fullTableKey.keyParts.runningReduce { prev, cur -> "$prev.$cur" } + + // short table name (only the name without parental prefix, like a - it is used in decoder and encoder) + public override val name: String = fullTableKey.keyParts.last().trimQuotes() + public abstract val type: TableType + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + content: String, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "", + config: TomlConfig, + isSynthetic: Boolean = false + ) : this( + TomlKey(content.takeBeforeComment(config.allowEscapedQuotesInLiteralStrings).trim('[', ']'), lineNo), + // Todo: Temporary workaround until this constructor is removed + lineNo, + comments, + inlineComment, + isSynthetic + ) + + override fun write( + emitter: TomlEmitter, + config: TomlOutputConfig + ) { + // Todo: Option to explicitly define super tables? + // Todo: Support dotted key-value pairs (i.e. a.b.c.d = 7) + + val firstChild = children.first() + + if (isExplicit(firstChild) && type == TableType.PRIMITIVE) { + emitter.writeHeader(config) + + if (inlineComment.isNotEmpty()) { + emitter.emitComment(inlineComment, inline = true) + } + + if (firstChild !is TomlStubEmptyNode) { + emitter.emitNewLine() + } + + emitter.indent() + + emitter.writeChildren(children, config) + + emitter.dedent() + } else { + emitter.writeChildren(children, config) + } + } + + protected abstract fun TomlEmitter.writeHeader( + config: TomlOutputConfig + ) + + /** + * Determines whether the table should be explicitly defined. Synthetic tables + * are implicit except when: + * - the first child is a pair, and there are other children (to exclude + * dotted pairs) + * - the table is empty + */ + private fun isExplicit( + firstChild: TomlNode + ) = if (!isSynthetic) { + true + } else { + when (firstChild) { + is TomlStubEmptyNode -> true + is TomlKeyValue, + is TomlInlineTable -> children.size > 1 + else -> false + } + } + + override fun toString(): String = buildString { + val config = TomlOutputConfig() + val emitter = TomlStringEmitter(this, config) + + emitter.writeHeader(config) + } + + public companion object { + @JvmStatic + protected fun parseSection( + content: String, + lineNo: Int, + isArray: Boolean + ): TomlKey { + val close = if (isArray) "]]" else "]" + + val lastIndexOfBrace = content.lastIndexOf(close) + if (lastIndexOfBrace == -1) { + throw ParseException( + "Invalid Tables provided: $content." + + " It has missing closing bracket${if (isArray) "s" else ""}: '$close'", lineNo + ) + } + val sectionFromContent = content.takeBeforeComment(false).trim().let { + if (isArray) { + it.trimDoubleBrackets() + } else { + it.trimBrackets() + } + } + .trim() + + if (sectionFromContent.isBlank()) { + throw ParseException("Incorrect blank name for ${if (isArray) "array of tables" else "table"}: $content", lineNo) + } + + return TomlKey(sectionFromContent, lineNo) + } + } +} + +/** + * Special Enum that is used in a logic related to insertion of tables to AST + */ +public enum class TableType { + ARRAY, + PRIMITIVE, + ; +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTablePrimitive.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTablePrimitive.kt new file mode 100644 index 00000000..ad647128 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTablePrimitive.kt @@ -0,0 +1,126 @@ +/** + * File contains all classes used in Toml AST node + */ + +package com.akuleshov7.ktoml.tree.nodes + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.parsers.* +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey +import com.akuleshov7.ktoml.writers.TomlEmitter + +/** + * tablesList - a list of names of sections (tables) that are included into this particular TomlTable + * for example: if the TomlTable is [a.b.c] this list will contain [a], [a.b], [a.b.c] + * @property isSynthetic - flag to determine that this node was synthetically and there is no such table in the input + */ +public class TomlTablePrimitive( + fullTableKey: TomlKey, + lineNo: Int, + comments: List, + inlineComment: String, + isSynthetic: Boolean = false +) : TomlTable( + fullTableKey, + lineNo, + comments, + inlineComment, + isSynthetic +) { + public override val type: TableType = TableType.PRIMITIVE + + public constructor( + content: String, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "", + isSynthetic: Boolean = false + ) : this( + parseSection(content, lineNo, isArray = false), + lineNo, + comments, + inlineComment, + isSynthetic + ) + + @Deprecated( + message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." + ) + public constructor( + content: String, + lineNo: Int, + comments: List = emptyList(), + inlineComment: String = "", + config: TomlConfig, + isSynthetic: Boolean = false + ) : this( + content, + lineNo, + comments, + inlineComment, + isSynthetic + ) + + override fun TomlEmitter.writeHeader( + config: TomlOutputConfig + ) { + startTableHeader() + + fullTableKey.write(emitter = this) + + endTableHeader() + } + + override fun TomlEmitter.writeChildren( + children: List, + config: TomlOutputConfig + ) { + if (children.first() is TomlStubEmptyNode) { + return + } + + var prevChild: TomlNode? = null + + children.forEachIndexed { i, child -> + writeChildComments(child) + + // Declare the super table after a nested table, to avoid a pair being + // a part of the previous table by mistake. + if ((child is TomlKeyValue || child is TomlInlineTable) && prevChild is TomlTable) { + dedent() + + emitNewLine() + emitIndent() + writeHeader(config) + emitNewLine() + + indent() + indent() + } + + when (child) { + is TomlTablePrimitive -> + if (!child.isSynthetic && child.getFirstChild() !is TomlTable) { + emitIndent() + } + is TomlArrayOfTables -> { } + else -> emitIndent() + } + + child.write(emitter = this, config) + writeChildInlineComment(child) + + if (i < children.lastIndex) { + emitNewLine() + // A single newline follows single-line pairs, except when a table + // follows. Two newlines follow multi-line pairs. + if ((child is TomlKeyValue && child.isMultiline()) || children[i + 1] is TomlTable) { + emitNewLine() + } + } + + prevChild = child + } + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Keys.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Keys.kt new file mode 100644 index 00000000..875b862d --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Keys.kt @@ -0,0 +1,35 @@ +/** + * Contains functions to determine the type of key during encoding and writing. + */ + +package com.akuleshov7.ktoml.utils + +private val bareKeyCharSet = (('A'..'Z') + ('a'..'z') + ('0'..'9') + '-' + '_') + .toCharArray() + .concatToString() + +/** + * Returns true if the specified key is a bare key, a key containing only + * alphanumeric, underscore, and hyphen characters. + * + * @return `true` if this key is a bare key. + */ +internal fun String.isBareKey() = all { it in bareKeyCharSet } + +/** + * Returns true if the specified key contains at least one unescaped double + * quote and no single quotes. + * + * @return `true` if this key can be written as a literal key. + */ +internal fun String.isLiteralKeyCandidate(): Boolean { + for ((i, char) in withIndex()) { + if (char == '\'') { + return false + } else if (char == '"' && (i == 0 || this[i - 1] != '\\')) { + return true + } + } + + return false +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Regexes.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Regexes.kt deleted file mode 100644 index 1b791de7..00000000 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Regexes.kt +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Regexes used in tree writing. - */ - -package com.akuleshov7.ktoml.utils - -/** - * Matches control characters not allowed in strings. Specifically, this matches - * Unicode block [`Cc`](https://www.compart.com/en/unicode/category/Cc) excluding - * tab. - * - * Used to replace these characters with valid Unicode escapes when writing basic - * strings, or throw an exception for them when writing literal strings. - */ -internal val controlCharacterRegex: Regex = - Regex("""[\x00-\x08\x0A-\x1F\x7F\x80-\x9F]""") - -/** - * Matches an unescaped backlash character. A backslash will be excluded if followed - * by: - * - another backslash - * - any of the "compact escapes" (i.e. `\t`, `\"`, etc.) - * - a Unicode escape, "u" or "U" followed by 4 or 8 hex digits respectively - */ -internal val unescapedBackslashRegex: Regex = - Regex("""\\\\|\\(?![btnfr"]|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})""") diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/SpecialCharacters.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/SpecialCharacters.kt new file mode 100644 index 00000000..adeefadd --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/SpecialCharacters.kt @@ -0,0 +1,190 @@ +/** + * This file contains utility methods for correct processing of escaped characters. + * This logic is used for processing of Chars, Literal Strings and basic Strings. + * + * In TOML we need properly process escaped symbols like '\t', '\n', unicode symbols and other. + * For Literal Strings ('') these symbols should be parsed "as is", for basic strings ("") and chars ('') + * they should be decoded to proper characters. + */ + +package com.akuleshov7.ktoml.utils + +import com.akuleshov7.ktoml.exceptions.UnknownEscapeSymbolsException + +internal const val COMPLEX_UNICODE_LENGTH = 8 +internal const val COMPLEX_UNICODE_PREFIX = 'U' +internal const val HEX_RADIX = 16 +internal const val SIMPLE_UNICODE_LENGTH = 4 +internal const val SIMPLE_UNICODE_PREFIX = 'u' + +/** + * Converting special escaped symbols like newlines, tabs and unicode symbols to proper characters for decoding + * + * @param lineNo line number of a string + * @return returning a string with converted escaped special symbols + * @throws ParseException if unknown escaped symbols were used + * @throws UnknownEscapeSymbolsException + */ +public fun String.convertSpecialCharacters(lineNo: Int): String { + val resultString = StringBuilder() + var i = 0 + while (i < length) { + val currentChar = get(i) + var offset = 1 + if (currentChar == '\\' && i != lastIndex) { + // Escaped + val next = get(i + 1) + offset++ + when (next) { + 't' -> resultString.append('\t') + 'b' -> resultString.append('\b') + 'r' -> resultString.append('\r') + 'n' -> resultString.append('\n') + 'f' -> resultString.append('\u000C') + '\\' -> resultString.append('\\') + '\'' -> resultString.append('\'') + '"' -> resultString.append('"') + SIMPLE_UNICODE_PREFIX, COMPLEX_UNICODE_PREFIX -> + offset += resultString.appendEscapedUnicode(this, next, i + 2, lineNo) + + else -> throw UnknownEscapeSymbolsException("\\$next", lineNo) + } + } else { + resultString.append(currentChar) + } + i += offset + } + return resultString.toString() +} + +/** + * Escaping and converting unicode symbols for decoding + * + * @param fullString + * @param marker + * @param codeStartIndex + * @param lineNo line number of a string + * @return position of + * @throws ParseException + * @throws UnknownEscapeSymbolsException + */ +public fun StringBuilder.appendEscapedUnicode( + fullString: String, + marker: Char, + codeStartIndex: Int, + lineNo: Int +): Int { + val nbUnicodeChars = if (marker == SIMPLE_UNICODE_PREFIX) { + SIMPLE_UNICODE_LENGTH + } else { + COMPLEX_UNICODE_LENGTH + } + if (codeStartIndex + nbUnicodeChars > fullString.length) { + val invalid = fullString.substring(codeStartIndex - 1) + throw UnknownEscapeSymbolsException("\\$invalid", lineNo) + } + val hexCode = fullString.substring(codeStartIndex, codeStartIndex + nbUnicodeChars) + val codePoint = hexCode.toInt(HEX_RADIX) + try { + appendCodePointCompat(codePoint) + } catch (e: IllegalArgumentException) { + throw UnknownEscapeSymbolsException("\\$marker$hexCode", lineNo) + } + return nbUnicodeChars +} + +/** + * Escaping special characters for encoding + * + * @param multiline + * @return converted string with escaped special symbols + */ +public fun String.escapeSpecialCharacters(multiline: Boolean = false): String = + if (multiline) { + escapeControlChars(Char::isMultilineControlChar) + .replace("\"\"\"", "\"\"\\\"") + .escapeBackslashes("btnfruU\"\r\n") + } else { + escapeControlChars(Char::isControlChar) + .replace("\"", "\\\"") + .escapeBackslashes("btnfruU\"") + } + +/** + * Checks if a character is an invalid control character for strings. Specifically, + * this returns true for characters in Unicode block [`Cc`](https://www.compart.com/en/unicode/category/Cc) + * excluding tab. + * + * Used to replace these characters with valid Unicode escapes when writing basic + * strings, or throw an exception for them when writing literal strings. + * + * @return `true` if the character is a control character, except tab. + */ +internal fun Char.isControlChar() = this in CharCategory.CONTROL && this != '\t' + +/** + * Checks if a character is an invalid control character for multiline strings. + * This is the same as [isControlChar], but excludes line terminators. + * + * @return `true` if the character is a control character, except tab and line + * terminators. + */ +internal fun Char.isMultilineControlChar() = isControlChar() && this !in "\n\r" + +private inline fun String.escapeControlChars(predicate: (Char) -> Boolean): String { + val sb = StringBuilder(length) + var last = 0 + for ((i, char) in withIndex()) { + if (predicate(char)) { + sb.append(this, last, i) + .append(char.escapeControlChar()) + last = i + 1 + } + } + + if (last < length) { + sb.append(this, last, length) + } + + return sb.toString() +} + +private fun Char.escapeControlChar() = when (this) { + '\t' -> "\\t" + '\b' -> "\\b" + '\n' -> "\\n" + '\u000C' -> "\\f" + '\r' -> "\\r" + else -> { + val hexDigits = code.toString(HEX_RADIX) + + "\\$SIMPLE_UNICODE_PREFIX${ + hexDigits.padStart(SIMPLE_UNICODE_LENGTH, '0') + }" + } +} + +private fun String.escapeBackslashes(escapes: String): String { + val sb = StringBuilder(length) + var slashCount = 0 + var last = 0 + for ((i, char) in withIndex()) { + if (char == '\\') { + slashCount++ + } else { + if (slashCount > 0 && char !in escapes && slashCount % 2 != 0) { + sb.append(this, last, i - 1) + .append("\\\\$char") + last = i + 1 + } + + slashCount = 0 + } + } + + if (last < length) { + sb.append(this, last, length) + } + + return sb.toString() +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Types.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Types.kt new file mode 100644 index 00000000..ad28372a --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Types.kt @@ -0,0 +1,36 @@ +/** + * File contains enums with minimum and maximum values of corresponding types + */ + +package com.akuleshov7.ktoml.utils + +/** + * @property min + * @property max + */ +public enum class IntegerLimitsEnum(public val min: Long, public val max: Long) { + BYTE(Byte.MIN_VALUE.toLong(), Byte.MAX_VALUE.toLong()), + CHAR(Char.MIN_VALUE.code.toLong(), Char.MAX_VALUE.code.toLong()), + INT(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()), + LONG(Long.MIN_VALUE, Long.MAX_VALUE), + SHORT(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()), + ; + // Unsigned values are not supported now, and I think + // that will not be supported, because TOML spec says the following: + // Arbitrary 64-bit signed integers (from −2^63 to 2^63−1) should be accepted and handled losslessly. + // If an integer cannot be represented losslessly, an error must be thrown. + // U_BYTE(UByte.MIN_VALUE.toLong(), UByte.MAX_VALUE.toLong()), + // U_SHORT(UShort.MIN_VALUE.toLong(), UShort.MAX_VALUE.toLong()), + // U_INT(UInt.MIN_VALUE.toLong(), UInt.MAX_VALUE.toLong()), + // U_LONG(ULong.MIN_VALUE.toLong(), ULong.MAX_VALUE.toLong()), +} + +/** + * @property min + * @property max + */ +public enum class FloatingPointLimitsEnum(public val min: Double, public val max: Double) { + DOUBLE(-Double.MAX_VALUE, Double.MAX_VALUE), + FLOAT(-Float.MAX_VALUE.toDouble(), Float.MAX_VALUE.toDouble()), + ; +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Utils.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Utils.kt index 7e68f1fa..731762e7 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Utils.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/Utils.kt @@ -4,8 +4,8 @@ package com.akuleshov7.ktoml.utils -import com.akuleshov7.ktoml.tree.TomlNode -import com.akuleshov7.ktoml.tree.TomlTablePrimitive +import com.akuleshov7.ktoml.tree.nodes.TomlNode +import com.akuleshov7.ktoml.tree.nodes.TomlTablePrimitive /** * Append a code point to a [StringBuilder] @@ -27,7 +27,7 @@ public fun findPrimitiveTableInAstByName(children: List, fullTableName return null } children.forEach { - if (it is TomlTablePrimitive && it.fullTableName == fullTableName) { + if (it is TomlTablePrimitive && it.fullTableKey.toString() == fullTableName) { return it } } diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/IntegerRepresentation.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/IntegerRepresentation.kt index 0a17522b..4512991e 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/IntegerRepresentation.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/IntegerRepresentation.kt @@ -3,17 +3,37 @@ package com.akuleshov7.ktoml.writers /** * How a TOML integer should be represented during encoding. * - * @property BINARY A binary number prefixed with `0b`. - * @property DECIMAL A decimal number. - * @property GROUPED A grouped decimal number, such as `1_000_000`. Todo: Add support. - * @property HEX A hexadecimal number prefixed with `0x`. - * @property OCTAL An octal number prefixed with `0o`. + * @property prefix The prefix, if any, signalling this representation in TOML. + * @property radix The radix or base number of the representation. */ -public enum class IntegerRepresentation { - BINARY, +@Suppress("MAGIC_NUMBER") +public enum class IntegerRepresentation( + public val prefix: String = "", + public val radix: Int = 10, +) { + /** + * A binary number prefixed with `0b`. + */ + BINARY("0b", 2), + + /** + * A decimal number. + */ DECIMAL, + + /** + * A grouped decimal number, such as `1_000_000`. + */ GROUPED, - HEX, - OCTAL, + + /** + * A hexadecimal number prefixed with `0x`. + */ + HEX("0x", 16), + + /** + * An octal number prefixed with `0o`. + */ + OCTAL("0o", 8), ; } 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 17ba5efd..db50621a 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 @@ -1,17 +1,18 @@ package com.akuleshov7.ktoml.writers -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.utils.isBareKey +import com.akuleshov7.ktoml.utils.isLiteralKeyCandidate import com.akuleshov7.ktoml.writers.IntegerRepresentation.* - -import kotlin.jvm.JvmStatic import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime /** * Abstracts the specifics of writing TOML files into "emit" operations. */ -public abstract class TomlEmitter(config: TomlConfig) { +public abstract class TomlEmitter(config: TomlOutputConfig) { private val indentation = config.indentation.value /** @@ -85,12 +86,12 @@ public abstract class TomlEmitter(config: TomlConfig) { * 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 + * @param inline Whether the comment is at the end of a line, e.g. after a * table header. * @return this instance */ - public fun emitComment(comment: String, endOfLine: Boolean = false): TomlEmitter = - emit(if (endOfLine) " # " else "# ") + public fun emitComment(comment: String, inline: Boolean = false): TomlEmitter = + emit(if (inline) " # " else "# ") .emit(comment) /** @@ -102,10 +103,10 @@ public abstract class TomlEmitter(config: TomlConfig) { * @return this instance */ public fun emitKey(key: String): TomlEmitter = - if (key matches bareKeyRegex) { + if (key.isBareKey()) { emitBareKey(key) } else { - emitQuotedKey(key, isLiteral = key matches literalKeyCandidateRegex) + emitQuotedKey(key, isLiteral = key.isLiteralKeyCandidate()) } /** @@ -179,7 +180,6 @@ public abstract class TomlEmitter(config: TomlConfig) { val quotes = if (isLiteral) "'''" else "\"\"\"" emit(quotes) - .emitNewLine() .emit(string) .emit(quotes) } else { @@ -195,22 +195,36 @@ public abstract class TomlEmitter(config: TomlConfig) { * * @param integer * @param representation How the integer will be represented in TOML. + * @param groupSize The digit group size, or less than `1` for no grouping. For + * example, a group size of `3` emits `1_000_000`, `4` emits `0b1111_1111`, etc. * @return this instance */ - public fun emitValue(integer: Long, representation: IntegerRepresentation = DECIMAL): TomlEmitter = - when (representation) { - DECIMAL -> 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() - } + @Suppress("SAY_NO_TO_VAR") + public fun emitValue( + integer: Long, + representation: IntegerRepresentation = DECIMAL, + groupSize: Int = 0 + ): TomlEmitter { + // Todo: Add groupSize to the annotation and AST and remove GROUPED. + if (representation == GROUPED) { + return emitValue(integer, representation = DECIMAL, groupSize = 3) + } + + if (integer < 0) { + emit('-') + } + + var digits = integer.toString(representation.radix).trimStart('-') + + if (groupSize > 0) { + digits = (digits as CharSequence).reversed() + .chunked(groupSize, CharSequence::reversed) + .asReversed() + .joinToString(separator = "_") + } + + return emit(representation.prefix).emit(digits) + } /** * Emits a floating-point value. @@ -221,9 +235,11 @@ public abstract class TomlEmitter(config: TomlConfig) { public fun emitValue(float: Double): TomlEmitter = emit(when { float.isNaN() -> "nan" - float.isInfinite() -> - if (float > 0) "inf" else "-inf" - else -> float.toString() + float.isInfinite() -> if (float > 0) "inf" else "-inf" + // Whole-number floats are formatted as integers on JS. + else -> float.toString().let { + if ('.' in it) it else "$it.0" + } }) /** @@ -258,6 +274,14 @@ public abstract class TomlEmitter(config: TomlConfig) { */ public fun emitValue(date: LocalDate): TomlEmitter = emit(date.toString()) + /** + * Emits a [LocalTime] value. + * + * @param time + * @return this instance + */ + public fun emitValue(time: LocalTime): TomlEmitter = emit(time.toString()) + /** * Emits a null value. * @@ -306,16 +330,4 @@ public abstract class TomlEmitter(config: TomlConfig) { * @return this instance */ public fun emitPairDelimiter(): TomlEmitter = 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("""[^'"]*((?) +@Serializable +data class SimpleArrayWithNullableValues(val a: List) + @Serializable data class SimpleStringArray(val a: List) @@ -74,7 +79,7 @@ class SimpleArrayDecoderTest { val testWithNullArray2: ClassWithMutableList = Toml.decodeFromString("field = null") assertEquals(null, testWithNullArray2.field) - assertFailsWith { Toml.decodeFromString("field = [null]").field } + assertFailsWith { Toml.decodeFromString("field = [null]").field } val testWithOnlyNullInArray: ClassWithImmutableList = Toml.decodeFromString("field = [null ]") assertEquals(listOf(null), testWithOnlyNullInArray.field) @@ -83,20 +88,25 @@ class SimpleArrayDecoderTest { assertEquals(listOf(null, 1), testWithNullInArray.field) } - @Test fun testSimpleArrayDecoder() { val test = "a = [1, 2, 3]" assertEquals(SimpleArray(listOf(1, 2, 3)), Toml.decodeFromString(test)) } + @Test + fun testArrayWithTrailingComma() { + val test = "a = [1, 2, 3,]" + assertEquals(SimpleArrayWithNullableValues(listOf(1, 2, 3)), Toml.decodeFromString(test)) + } + @Test fun testSimpleArrayDecoderInNestedTable() { var test = """ - |[table] - |name = "my name" - |configurationList = ["a", "b", "c"] - """.trimMargin() + [table] + name = "my name" + configurationList = ["a", "b", "c"] + """ assertEquals( ArrayInInlineTable( @@ -106,10 +116,10 @@ class SimpleArrayDecoderTest { test = """ - |[table] - |configurationList = ["a", "b", "c"] - |name = "my name" - """.trimMargin() + [table] + configurationList = ["a", "b", "c"] + name = "my name" + """ assertEquals( ArrayInInlineTable( @@ -118,11 +128,11 @@ class SimpleArrayDecoderTest { ) val testTable = """ - |configurationList1 = ["a", "b", "c"] - |configurationList2 = ["a", "b", "c"] - |[table] - |name = "my name" - """.trimMargin() + configurationList1 = ["a", "b", "c"] + configurationList2 = ["a", "b", "c"] + [table] + name = "my name" + """ assertEquals( TestArrays( @@ -133,13 +143,13 @@ class SimpleArrayDecoderTest { ) val testTableAndVariables = """ - |name1 = "simple" - |configurationList1 = ["a", "b", "c"] - |name2 = "simple" - |configurationList2 = ["a", "b", "c"] - |[table] - |name = "my name" - """.trimMargin() + name1 = "simple" + configurationList1 = ["a", "b", "c"] + name2 = "simple" + configurationList2 = ["a", "b", "c"] + [table] + name = "my name" + """ assertEquals( diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ArrayOfTablesDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ArrayOfTablesDecoderTest.kt index 987f58d0..f7594599 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ArrayOfTablesDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ArrayOfTablesDecoderTest.kt @@ -1,14 +1,8 @@ package com.akuleshov7.ktoml.decoders -import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.exceptions.CastException -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.test.Ignore import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith @Serializable data class TomlArrayOfTables(val a: List) diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CharDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CharDecoderTest.kt new file mode 100644 index 00000000..8de48e70 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CharDecoderTest.kt @@ -0,0 +1,69 @@ +package com.akuleshov7.ktoml.decoders + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.exceptions.IllegalTypeException +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CharDecoderTest { + @Serializable + data class MyClass( + val a: Char, + val b: Char, + val c: Char, + ) + + @Test + fun charBasicTest() { + val test = + """ + a = 123 + b = '4' + c = 'D' + """ + + val decoded = Toml.decodeFromString(test) + assertEquals(decoded, MyClass('{', '4', 'D')) + } + + @Test + fun charLiteralBasicTest() { + val test = + """ + a = '\r' + b = '\n' + c = '\t' + """ + + val decoded = Toml.decodeFromString(test) + assertEquals(decoded, MyClass('\r', '\n', '\t')) + } + + @Test + fun charUnicodeSymbolsTest() { + val test = + """ + a = '\u0048' + b = '\u0065' + c = '\u006C' + """ + + val decoded = Toml.decodeFromString(test) + assertEquals(decoded, MyClass('H', 'e', 'l')) + } + + @Test + fun charSeveralUnicodeSymbolsTest() { + val test = + """ + a = '\u0048\u0065' + b = '\u0065\t' + c = '\u006Cdd' + """ + + assertFailsWith { Toml.decodeFromString(test) } + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt index 9c200ed9..39ebcd4a 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt @@ -63,9 +63,10 @@ class CustomSerializerTest { """ rgb = "0" brg = "1" - """.trimIndent() + """ ) ) + UInt.MAX_VALUE } @Serializable @@ -74,13 +75,13 @@ class CustomSerializerTest { @Test @Ignore fun testDecodingWithCustomSerializer() { - println(Toml.decodeFromString( + Toml.decodeFromString( """ [background] rgb = "0" [foreground] rgb = "0" - """.trimIndent() - )) + """ + ) } } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DateTimeDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DateTimeDecoderTest.kt index d8e924ee..2d351b67 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DateTimeDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DateTimeDecoderTest.kt @@ -16,6 +16,7 @@ class DateTimeDecoderTest { val instants: List, val localDateTimes: List, val localDate: LocalDate, + val localTime: LocalTime, val dateInString: String ) @@ -25,6 +26,7 @@ class DateTimeDecoderTest { instants = [1979-05-27T07:32:00Z, 1979-05-27T00:32:00-07:00, 1979-05-27T00:32:00.999999-07:00, 1979-05-27 07:32:00Z] localDateTimes = [1979-05-27T07:32:00, 1979-05-27T00:32:00.999999] localDate = 1979-05-27 + localTime = 07:45:33 dateInString = "1979-05-27T00:32:00-07:00" """.trimIndent() val expectedInstants = listOf( @@ -42,11 +44,13 @@ class DateTimeDecoderTest { LocalDateTime(1979, 5, 27, 0, 32, 0, 999999000) ) val expectedLocalDate = LocalDate(1979, 5, 27) + val expectedLocalTime = LocalTime(7, 45, 33) assertEquals( TimeTable( expectedInstants, expectedLocalDateTimes, expectedLocalDate, + expectedLocalTime, "1979-05-27T00:32:00-07:00" ), Toml().decodeFromString(toml) @@ -62,6 +66,9 @@ class DateTimeDecoderTest { @Serializable data class InvalidDate(val date: LocalDate) + @Serializable + data class InvalidTime(val time: LocalTime) + @Test fun testInvalidData() { assertFailsWith { @@ -73,5 +80,8 @@ class DateTimeDecoderTest { assertFailsWith { Toml.decodeFromString("date=1979/05/27") } + assertFailsWith { + Toml.decodeFromString("time=07/35/12") + } } } \ No newline at end of file diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DecodingTypeTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DecodingTypeTest.kt index 5f6b03da..7d832f84 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DecodingTypeTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DecodingTypeTest.kt @@ -2,7 +2,6 @@ package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.exceptions.IllegalTypeException -import com.akuleshov7.ktoml.exceptions.CastException import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlin.test.Test @@ -44,9 +43,9 @@ class DecodingTypeTest { assertFailsWith { Toml.decodeFromString("a = true") } assertFailsWith { Toml.decodeFromString("a = true") } - assertFailsWith { Toml.decodeFromString("a = \"test\"") } - assertFailsWith { Toml.decodeFromString("a = true") } - assertFailsWith { Toml.decodeFromString("a = 12.0") } - assertFailsWith { Toml.decodeFromString("a = 1") } + assertFailsWith { Toml.decodeFromString("a = \"test\"") } + assertFailsWith { Toml.decodeFromString("a = true") } + assertFailsWith { Toml.decodeFromString("a = 12.0") } + assertFailsWith { Toml.decodeFromString("a = 1") } } } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DottedKeysDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DottedKeysDecoderTest.kt index e8dba493..1f8e1b2f 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DottedKeysDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DottedKeysDecoderTest.kt @@ -1,7 +1,7 @@ package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -110,16 +110,16 @@ class DottedKeysDecoderTest { ), Toml().decodeFromString( """ - |table2.b.d = 2 - |[table1] - |a.c = 1 - |[table1.table2] - |b.b.a = 1 - |[table2] - |b.f = 2 - |table2."foo bar".d = 2 - |[table3] - """.trimMargin() + table2.b.d = 2 + [table1] + a.c = 1 + [table1.table2] + b.b.a = 1 + [table2] + b.f = 2 + table2."foo bar".d = 2 + [table3] + """ ) ) } @@ -128,13 +128,13 @@ class DottedKeysDecoderTest { fun tableTest() { assertEquals( SimpleNestedExample(table2 = Table4(b = B(f = 2, d = 2), e = 5)), - Toml(TomlConfig(true)).decodeFromString( + Toml(TomlInputConfig(true)).decodeFromString( """ - |table2.b.d = 2 - |[table2] - |e = 5 - |b.f = 2 - """.trimMargin() + table2.b.d = 2 + [table2] + e = 5 + b.f = 2 + """ ) ) } @@ -143,15 +143,15 @@ class DottedKeysDecoderTest { fun tableAndDottedKeys() { assertEquals( SimpleNestedExample(table2 = Table4(b = B(f = 7, d = 2), e = 6)), - Toml(TomlConfig(true)).decodeFromString( + Toml(TomlInputConfig(true)).decodeFromString( """ - |[table2] - |table2."foo bar".d = 2 - |e = 6 - |[table2.b] - |d = 2 - |f = 7 - """.trimMargin() + [table2] + table2."foo bar".d = 2 + e = 6 + [table2.b] + d = 2 + f = 7 + """ ) ) } @@ -208,14 +208,14 @@ class DottedKeysDecoderTest { ), Toml.decodeFromString( """ - |[a."b.c..".d."e.f"] - | val = 1 - | [a] - | [a."b.c.."] - | val = 2 - | [a."b.c..".inner] - | val = 3 - """.trimMargin() + [a."b.c..".d."e.f"] + val = 1 + [a] + [a."b.c.."] + val = 2 + [a."b.c..".inner] + val = 3 + """ ) ) } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt index fbff18f5..beb39b82 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt @@ -1,7 +1,7 @@ package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig import com.akuleshov7.ktoml.exceptions.InvalidEnumValueException import com.akuleshov7.ktoml.exceptions.MissingRequiredPropertyException import com.akuleshov7.ktoml.exceptions.ParseException @@ -125,10 +125,12 @@ class GeneralDecoderTest { @Test fun testForSimpleNestedTable() { - val test = ("c = 5 \n" + - "[table1] \n" + - " b = 6 \n" + - " a = 5 \n ") + val test = """ + c = 5 + [table1] + b = 6 + a = 5 + """.trimIndent() assertEquals(NestedSimpleTable(5, Table1(5, 6)), Toml.decodeFromString(test)) } @@ -178,7 +180,7 @@ class GeneralDecoderTest { ) { // 'err' key is unknown, but this should not trigger an error becuase of ignoreUnknown // 'b' key is not provided and should trigger an error - Toml(TomlConfig(true)).decodeFromString( + Toml(TomlInputConfig(true)).decodeFromString( " a = true \n" + " d = 5 \n" + " e = \"my test\"\n" + @@ -193,7 +195,7 @@ class GeneralDecoderTest { "Invalid number of arguments provided for deserialization." + " Missing required field in the input" ) { - Toml(TomlConfig(true)).decodeFromString( + Toml(TomlInputConfig(true)).decodeFromString( "[tableUNKNOWN] \n" + " a = true \n" + " d = 5 \n" + @@ -209,7 +211,7 @@ class GeneralDecoderTest { " b = \"A\" \n" + " d = 55 \n") - assertEquals(Table3(true, b = TestEnum.A, d = 55), Toml(TomlConfig(true)).decodeFromString(test)) + assertEquals(Table3(true, b = TestEnum.A, d = 55), Toml(TomlInputConfig(true)).decodeFromString(test)) // e is missing, because it has a default value @@ -222,7 +224,7 @@ class GeneralDecoderTest { "a = true \n" + " b = \"A\" \n" - Toml(TomlConfig(true)).decodeFromString(failMissing) + Toml(TomlInputConfig(true)).decodeFromString(failMissing) } } @@ -236,7 +238,7 @@ class GeneralDecoderTest { "f = NIL\n") assertEquals(NullableValues(null, null, null, null, null, null), Toml.decodeFromString(test)) assertFailsWith { - Toml(TomlConfig(allowNullValues = false)).decodeFromString(test) + Toml(TomlInputConfig(allowNullValues = false)).decodeFromString(test) } } @@ -246,7 +248,7 @@ class GeneralDecoderTest { "Invalid number of arguments provided for deserialization." + " Missing required field in the input" ) { - Toml(TomlConfig(true)).decodeFromString("[table3] \n a = true") + Toml(TomlInputConfig(true)).decodeFromString("[table3] \n a = true") } } @@ -256,7 +258,7 @@ class GeneralDecoderTest { "Invalid number of arguments provided for deserialization." + " Missing required field in the input" ) { - Toml(TomlConfig(true)).decodeFromString( + Toml(TomlInputConfig(true)).decodeFromString( "[table1] \n a = 5 \n b = 6" ) } @@ -269,7 +271,7 @@ class GeneralDecoderTest { assertEquals( ComplexPlainTomlCase(Table3(a = true, d = 5, b = TestEnum.B)), Toml( - TomlConfig(true) + TomlInputConfig(true) ).decodeFromString(test) ) } @@ -277,12 +279,12 @@ class GeneralDecoderTest { @Test fun testChildTableBeforeParent() { val test = """ - |[a.b] - | c = 5 - | [a] - | a = true - """.trimMargin() - TomlConfig(true) + [a.b] + c = 5 + [a] + a = true + """ + TomlInputConfig(true) assertEquals(ChildTableBeforeParent(A(B(5), true)), Toml.decodeFromString(test)) } @@ -290,7 +292,7 @@ class GeneralDecoderTest { @Test fun testIncorrectEnumValue() { assertFailsWith { - Toml(TomlConfig(true)).decodeFromString( + Toml(TomlInputConfig(true)).decodeFromString( ("a = true \n" + " b = \"F\"\n" + " e = \"my string\"\n" + @@ -374,20 +376,21 @@ class GeneralDecoderTest { @Test fun severalTablesOnTheSameLevel() { - val test = """|[table] - |[table.in1] - | a = 1 - | [table.in1.in1] - | a = 1 - | [table.in1.in2] - | a = 1 - |[table.in2] - | a = 1 - | [table.in2.in1] - | a = 1 - | [table.in2.in2] - | a = 1 - """.trimMargin() + val test = """ + [table] + [table.in1] + a = 1 + [table.in1.in1] + a = 1 + [table.in1.in2] + a = 1 + [table.in2] + a = 1 + [table.in2.in1] + a = 1 + [table.in2.in2] + a = 1 + """ assertEquals( MyTest( diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/IntegersDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/IntegersDecoderTest.kt new file mode 100644 index 00000000..1a48218e --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/IntegersDecoderTest.kt @@ -0,0 +1,76 @@ +package com.akuleshov7.ktoml.decoders + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.exceptions.IllegalTypeException +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.Serializable +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlinx.serialization.ExperimentalSerializationApi +import kotlin.test.assertFailsWith + +class IntegersDecoderTest { + @Serializable + data class Integers( + val s: Short, + val b: Byte, + val i: Int, + val l: Long, + ) + + @Test + fun positiveScenario() { + var test = """ + s = 5 + b = +5 + i = -5 + l = 5 + """ + + var decoded = Toml.decodeFromString(test) + assertEquals( + Integers(5, 5, -5, 5), + decoded + ) + + test = """ + s = 32767 + b = -128 + i = 5 + l = 5 + """ + + decoded = Toml.decodeFromString(test) + assertEquals( + Integers(32767, -128, 5, 5), + decoded + ) + } + + @Test + fun negativeScenario() { + var test = """ + s = 32768 + b = 5 + i = 5 + l = 5 + """.trimMargin() + assertFailsWith { Toml.decodeFromString(test) } + + test = """ + s = -32769 + b = 5 + i = 5 + l = 5 + """.trimMargin() + assertFailsWith { Toml.decodeFromString(test) } + + test = """ + s = 0.25 + b = 5 + i = 5 + l = 5 + """.trimMargin() + assertFailsWith { Toml.decodeFromString(test) } + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/NullableTablesTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/NullableTablesTest.kt index 2ea06c00..d3c9db99 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/NullableTablesTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/NullableTablesTest.kt @@ -2,6 +2,7 @@ package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlin.test.Test @@ -30,7 +31,7 @@ class NullableTablesTest { @Test fun nullableKey() { val mapper = Toml( - config = TomlConfig( + inputConfig = TomlInputConfig( ignoreUnknownNames = true, allowEmptyValues = true ) diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PrimitivesDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PrimitivesDecoderTest.kt new file mode 100644 index 00000000..ee1ee00a --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PrimitivesDecoderTest.kt @@ -0,0 +1,280 @@ +package com.akuleshov7.ktoml.decoders + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.exceptions.IllegalTypeException +import com.akuleshov7.ktoml.exceptions.ParseException +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + + +class PrimitivesDecoderTest { + @Test + fun decodeByte() { + fun test(expected: Byte, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Byte) + + val data = Toml.decodeFromString(toml) + + assertEquals(expected, data.value) + } + + test(0, "0") + test(1, "1") + test(-1, "-1") + test(-128, "-128") + test(127, "127") + } + + @Test + fun decodeByteFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Byte) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails("-129") + testFails("128") + } + + @Test + fun decodeChar() { + fun test(expected: Char, input: String) { + val toml = /*language=TOML*/ "value = $input" + + @Serializable + data class Data(val value: Char) + + val data = Toml.decodeFromString(toml) + assertEquals(expected, data.value) + } + + test('1', "\'1\'") + test((1).toChar(), "1") + test(Char.MAX_VALUE, "65535") + test(Char.MIN_VALUE, "0") + } + + @Test + fun decodeCharFailure() { + fun test(expected: Char, input: String) { + val toml = /*language=TOML*/ "value = $input" + + @Serializable + data class Data(val value: Char) + + assertFailsWith { + Toml.decodeFromString(toml) + } + } + + test((-1).toChar(), "-1") + test((65536).toChar(), "65536") + } + + @Test + fun decodeShort() { + fun test(expected: Short, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Short) + + val data = Toml.decodeFromString(toml) + + assertEquals(expected, data.value) + } + + test(0, "0") + test(1, "1") + test(-1, "-1") + test(-128, "-128") + test(127, "127") + } + + @Test + fun decodeShortFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Short) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails("${Short.MAX_VALUE.toInt() + 1}") + testFails("${Short.MIN_VALUE.toInt() - 1}") + } + + @Test + fun decodeInt() { + fun test(expected: Int, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Int) + + val data = Toml.decodeFromString(toml) + + assertEquals(expected, data.value) + } + + test(0, "0") + test(1, "1") + test(-1, "-1") + test(-128, "-128") + test(127, "127") + test(Int.MAX_VALUE, "${Int.MAX_VALUE}") + test(Int.MIN_VALUE, "${Int.MIN_VALUE}") + } + + @Test + fun decodeIntFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Int) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails("${Int.MIN_VALUE.toLong() - 1}") + testFails("${Int.MAX_VALUE.toLong() + 1}") + } + + @Test + fun decodeLong() { + fun test(expected: Long, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Long) + + val data = Toml.decodeFromString(toml) + + assertEquals(expected, data.value) + } + + test(0, "0") + test(1, "1") + test(-1, "-1") + test(-128, "-128") + test(127, "127") + test(Long.MIN_VALUE, "${Long.MIN_VALUE}") + test(Long.MAX_VALUE, "${Long.MAX_VALUE}") + } + + @Test + fun decodeLongFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Long) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails("${Long.MIN_VALUE}0") + testFails("${Long.MAX_VALUE}0") + } + + @Test + fun decodeFloat() { + fun test(expected: Float, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Float) + + val data = Toml.decodeFromString(toml) + assertEquals(expected, data.value) + } + + test(0f, "0.0") + test(1f, "1.0") + test(-1f, "-1.0") + test(-128f, "-128.0") + test(127f, "127.0") + test(Float.NEGATIVE_INFINITY, "-inf") + test(Float.POSITIVE_INFINITY, "+inf") + test(Float.NaN, "nan") + } + + @Test + fun decodeFloatFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Float) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails((-Double.MAX_VALUE).toString()) + testFails("-129") + testFails("128") + } + + @Test + fun decodeDouble() { + fun test(expected: Double, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Double) + + val data = Toml.decodeFromString(toml) + + assertEquals(expected, data.value) + } + test(0.0, "0.0") + test(1.0, "1.0") + test(-1.0, "-1.0") + test(-128.0, "-128.0") + test(127.0, "127.0") + test(Double.NEGATIVE_INFINITY, "-inf") + test(Double.POSITIVE_INFINITY, "+inf") + test(Double.NaN, "nan") + } + + @Test + fun decodeDoubleFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Double) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails("-129") + testFails("128") + testFails("0") + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt index f91e010a..a43d55ff 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt @@ -1,7 +1,6 @@ package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.tree.TomlInlineTable import kotlinx.serialization.decodeFromString import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -21,12 +20,13 @@ class ReadMeExampleTest { @Serializable data class Table1( - // nullable values, from toml you can pass null/nil/empty value to this kind of a field + // nullable property, from toml input you can pass "null"/"nil"/"empty" value (no quotes needed) to this field val property1: Long?, - // please note, that according to the specification of toml integer values should be represented with Long - val property2: Long, - // no need to pass this value as it has the default value and is NOT REQUIRED - val property3: Long = 5 + // please note, that according to the specification of toml integer values should be represented with Long, + // but we allow to use Int/Short/etc. Just be careful with overflow + val property2: Byte, + // no need to pass this value in the input as it has the default value and so it is NOT REQUIRED + val property3: Short = 5 ) @Serializable @@ -34,7 +34,11 @@ class ReadMeExampleTest { val someNumber: Long, @SerialName("akuleshov7.com") val inlineTable: NestedTable, - val otherNumber: Double + val otherNumber: Double, + // Char in a manner of Java/Kotlin is not supported in TOML, because single quotes are used for literal strings. + // However, ktoml supports reading Char from both single-char string and from it's integer code + val charFromString: Char, + val charFromInteger: Char ) @Serializable @@ -60,39 +64,45 @@ class ReadMeExampleTest { gradle-libs-like-property = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } [table1] - property1 = null # null is prohibited by the TOML spec, - property2 = 6 + # null is prohibited by the TOML spec, but allowed in ktoml for nullable types + # so for 'property1' null value is ok. Use: property1 = null + property1 = 100 + property2 = 6 [table2] - someNumber = 5 + someNumber = 5 [table2."akuleshov7.com"] - name = 'this is a "literal" string' - # empty lists are also supported - configurationList = ["a", "b", "c", null] + name = 'this is a "literal" string' + # empty lists are also supported + configurationList = ["a", "b", "c"] # such redeclaration of table2 # is prohibited in toml specification; # but ktoml is allowing it in non-strict mode: [table2] - otherNumber = 5.56 - - """.trimMargin() + otherNumber = 5.56 + # use single quotes + charFromString = 'a' + charFromInteger = 123 + """ val decoded = Toml.decodeFromString(test) assertEquals( MyClass( someBooleanProperty = true, - table1 = Table1(property1 = null, property2 = 6), + table1 = Table1(property1 = 100, property2 = 6), table2 = Table2( someNumber = 5, - inlineTable = NestedTable(name = "this is a \"literal\" string", overriddenName = listOf("a", "b", "c", null)), - otherNumber = 5.56 + inlineTable = NestedTable(name = "this is a \"literal\" string", overriddenName = listOf("a", "b", "c")), + otherNumber = 5.56, + charFromString = 'a', + charFromInteger = '{' ), + kotlinJvm = GradlePlugin("org.jetbrains.kotlin.jvm", Version("kotlin")) ), decoded ) } } - diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/RequiredAnnotation.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/RequiredAnnotation.kt new file mode 100644 index 00000000..1dc863ff --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/RequiredAnnotation.kt @@ -0,0 +1,24 @@ +package com.akuleshov7.ktoml.decoders + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.exceptions.MissingRequiredPropertyException +import kotlinx.serialization.Required +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlin.test.Test +import kotlin.test.assertFailsWith + +@Serializable +data class RequiredStr( + val a: String = "a", + @Required + val b: String = "b", + val c: String, +) + +class RequiredAnnotation { + @Test + fun testMissingRequiredPropertyException() { + assertFailsWith { Toml.decodeFromString("c = \"100\"") } + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt new file mode 100644 index 00000000..8c212e75 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt @@ -0,0 +1,161 @@ +package com.akuleshov7.ktoml.decoders + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.exceptions.ParseException +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.Serializable +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class StringDecoderTest { + @Serializable + data class Literals( + val winpath: String?, + val winpath2: String, + val quoted: String, + val regex: String, + ) + + @Test + fun positiveScenario() { + var test = """ + # What you see is what you get. + winpath = 'C:\Users\nodejs\templates' + winpath2 = '\\ServerX\admin${'$'}\system32\' + quoted = 'Tom "Dubs" Preston-Werner' + regex = '<\i\c*\s*>' + """ + + var decoded = Toml.decodeFromString(test) + assertEquals( + Literals( + "C:\\Users\\nodejs\\templates", + "\\\\ServerX\\admin${'$'}\\system32\\", + "Tom \"Dubs\" Preston-Werner", + "<\\i\\c*\\s*>" + ), + decoded + ) + + test = """ + winpath = '\t' + winpath2 = '\n' + quoted = '\r' + regex = '\f' + """ + + decoded = Toml.decodeFromString(test) + assertEquals( + Literals( + "\\t", + "\\n", + "\\r", + "\\f" + ), + decoded + ) + + test = """ + winpath = "\t" + winpath2 = "\n" + quoted = "\r" + regex = "\f" + """ + + decoded = Toml.decodeFromString(test) + assertEquals( + Literals( + "\t", + "\n", + "\r", + "\u000C" + ), + decoded + ) + + test = """ + winpath = "\u0048" + winpath2 = "\u0065" + quoted = "\u006C" + regex = "\u006F" + """ + + decoded = Toml.decodeFromString(test) + assertEquals( + Literals( + "H", + "e", + "l", + "o" + ), + decoded + ) + + test = """ + winpath = "\u0048\u0065\u006C\u006F" + winpath2 = "My\u0048\u0065\u006C\u006FWorld" + quoted = "\u0048\u0065\u006C\u006F World" + regex = "My\u0048\u0065\u006CWorld" + """ + + decoded = Toml.decodeFromString(test) + assertEquals( + Literals( + "Helo", + "MyHeloWorld", + "Helo World", + "MyHelWorld" + ), + decoded + ) + + test = """ + winpath = '\u0048\u0065\u006C\u006F' + winpath2 = 'My\u0048\u0065\u006C\u006FWorld' + quoted = '\u0048\u0065\u006C\u006F World' + regex = 'My\u0048\u0065\u006CWorld' + """ + + decoded = Toml.decodeFromString(test) + assertEquals( + Literals( + "\\u0048\\u0065\\u006C\\u006F", + "My\\u0048\\u0065\\u006C\\u006FWorld", + "\\u0048\\u0065\\u006C\\u006F World", + "My\\u0048\\u0065\\u006CWorld" + ), + decoded + ) + } + + @Test + fun emptyStringTest() { + var test = """ + winpath = + winpath2 = '' + quoted = "" + regex = '' + """ + + val decoded = Toml().decodeFromString(test) + assertEquals( + Literals( + null, + "", + "", + "" + ), + decoded + ) + + test = """ + winpath = + """.trimIndent() + + assertFailsWith { + Toml(TomlInputConfig(allowEmptyValues = false)).decodeFromString(test) + } + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt index 61dd567e..d2b3dce2 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt @@ -7,6 +7,7 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlin.test.Ignore import kotlin.test.Test +import kotlin.test.assertEquals class SurrogateTest { object ColorSerializer : KSerializer { @@ -35,14 +36,14 @@ class SurrogateTest { class Color(val rgb: Int) @Test - @Ignore fun testDecodingWithCustomSerializer() { - println(Toml.decodeFromString( + val test = Toml.decodeFromString( """ r = 5 g = 6 b = 7 """.trimIndent() - )) + ) + assertEquals(Color(329223).rgb, test.rgb) } } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/BasicMultilineStringDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/BasicMultilineStringDecoderTest.kt new file mode 100644 index 00000000..dc6840c6 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/BasicMultilineStringDecoderTest.kt @@ -0,0 +1,289 @@ +package com.akuleshov7.ktoml.decoders.multiline + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.exceptions.MissingRequiredPropertyException +import com.akuleshov7.ktoml.exceptions.ParseException +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class BasicMultilineStringDecoderTest { + + @Serializable + data class SimpleString(val a: String) + + @Serializable + data class SimpleStrings(val a: String, val b: String) + + @Serializable + data class StringAndInt(val a: String, val b: Int) + + @Serializable + data class StringAndInts(val a: String, val b: Int, val c: Int) + + private val tripleQuotes = "\"\"\"" + + @Test + fun testMultilineBasicStringDecode() { + var test = """ + a = ${tripleQuotes}abc${tripleQuotes} + """.trimIndent() + assertEquals(SimpleString("abc"), Toml.decodeFromString(test)) + + test = """ + + a = ${tripleQuotes}abc${tripleQuotes} + + b = 123 + """.trimIndent() + assertEquals(StringAndInt("abc", 123), Toml.decodeFromString(test)) + + test = """ + a = $tripleQuotes + first line # comment \ + second line$tripleQuotes + """.trimIndent() + assertEquals(SimpleString("first line # comment second line"), Toml.decodeFromString(test)) + + test = """ + a = ${tripleQuotes}first line + second line$tripleQuotes + """.trimIndent() + assertEquals(SimpleString("first line\nsecond line"), Toml.decodeFromString(test)) + + test = """ + a = ${tripleQuotes}first line + + second line$tripleQuotes + """.trimIndent() + assertEquals(SimpleString("first line\n\nsecond line"), Toml.decodeFromString(test)) + + test = "a = \"\"\"first line\n\t \nsecond line\"\"\"" + assertEquals(SimpleString("first line\n\t \nsecond line"), Toml.decodeFromString(test)) + + test = """ + a = $tripleQuotes + Roses are red + Violets are blue$tripleQuotes + """.trimIndent() + assertEquals(SimpleString("Roses are red\nViolets are blue"), Toml.decodeFromString(test)) + + test = """ + a = $tripleQuotes + + Test$tripleQuotes + b = 2 + """.trimIndent() + assertEquals( + StringAndInt("\nTest", 2), + Toml.decodeFromString(test) + ) + + test = """ + a = $tripleQuotes + Test + b = 32 + $tripleQuotes + """.trimIndent() + assertEquals( + SimpleString(a = "Test\nb = 32\n"), + Toml.decodeFromString(test) + ) + } + + @Test + fun lineEndingBackslash() { + var test = """ + a = $tripleQuotes + first line \ + second line \ + + third line$tripleQuotes + """.trimIndent() + assertEquals(SimpleString("first line second line third line"), Toml.decodeFromString(test)) + + test = """ + a = $tripleQuotes + The quick brown \ + + fox jumps over \ + the lazy dog.$tripleQuotes + """.trimIndent() + assertEquals(SimpleString("The quick brown fox jumps over the lazy dog."), Toml.decodeFromString(test)) + + test = """ + a = $tripleQuotes\ + The quick brown \ + fox jumps over \ + the lazy dog.\ + $tripleQuotes + """.trimIndent() + assertEquals(SimpleString("The quick brown fox jumps over the lazy dog."), Toml.decodeFromString(test)) + } + + @Test + fun testWithHashSymbol() { + val test = """ + a = $tripleQuotes + Roses are red # Not a comment + # Not a comment + Violets are blue$tripleQuotes + """.trimIndent() + assertEquals( + SimpleString("Roses are red # Not a comment\n# Not a comment \nViolets are blue"), + Toml.decodeFromString(test) + ) + } + + @Test + fun correctQuotesInsideBasic() { + var test = "a = \"\"\"Here are two quotation marks: \"\". Simple enough.\"\"\"" + + "\nb = 2" + assertEquals( + StringAndInt("Here are two quotation marks: \"\". Simple enough.", 2), + Toml.decodeFromString(test) + ) + + test = """ + a = ${tripleQuotes}Here are three quotation marks: ""\".$tripleQuotes + b = 2 + """.trimIndent() + assertEquals( + StringAndInt("Here are three quotation marks: \"\"\".", 2), + Toml.decodeFromString(test) + ) + + test = "a = \"\"\"Here are fifteen quotation marks: \"\"\\\"\"\"\\\"\"\"\\\"\"\"\\\"\"\"\\\".\"\"\"" + + "\nb = 2" + assertEquals( + StringAndInt("Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\".", 2), + Toml.decodeFromString(test) + ) + + test = "a = \"\"\"Here are fifteen quotation marks: \"\"\\\"\"\"\\\"\"\"\\\"\"\"\\\"\"\"\\\".\"\"\"" + + "\nb = 2" + assertEquals( + StringAndInt("Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\".", 2), + Toml.decodeFromString(test) + ) + + test = "a = \"\"\"\"This,\" she said, \"is just a pointless statement.\"\"\"\"" + + "\nb = 2" + assertEquals( + StringAndInt("\"This,\" she said, \"is just a pointless statement.\"", 2), + Toml.decodeFromString(test) + ) + + test = "a = \"\"\"\"\n\nThis,\" she said, \"is just a pointless statement.\"\n\n\"\"\"" + + "\nb = 2" + assertEquals( + StringAndInt("\"\n\nThis,\" she said, \"is just a pointless statement.\"\n\n", 2), + Toml.decodeFromString(test) + ) + } + + @Test + fun incorrectQuotesInside() { + val test = "a = \"\"\"Here are three quotation marks: \"\"\".\"\"\"" + val exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + } + + @Test + fun leadingNewLines() { + // "A newline immediately following the opening delimiter will be trimmed." + var test = """ + a = $tripleQuotes + + My String$tripleQuotes + """.trimIndent() + assertEquals( + SimpleString("\nMy String"), + Toml.decodeFromString(test) + ) + + test = """ + a = $tripleQuotes + My String$tripleQuotes + """.trimIndent() + assertEquals( + SimpleString("My String"), + Toml.decodeFromString(test) + ) + } + + @Test + fun incorrectString() { + var test = """ + a = ${tripleQuotes}Test String + """.trimIndent() + var exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + + test = """ + a = ${tripleQuotes}Test String" + """.trimIndent() + exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + + test = "a = \"\"\"Test String ''' " + exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + + test = """ + a = ${tripleQuotes}Test String " + b = "abc" + """.trimIndent() + exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + + test = """ + a = $tripleQuotes + + Test String + """.trimIndent() + exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + + test = """ + a = $tripleQuotes + + Test String + + """.trimIndent() + exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + } + + @Test + fun betweenOtherValues() { + val test = """ + b = 2 + a = ${tripleQuotes}Test + String + $tripleQuotes + c = 3 + """.trimIndent() + assertEquals( + StringAndInts("Test \nString\n", 2, 3), + Toml.decodeFromString(test) + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/LiteralMultilineStringDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/LiteralMultilineStringDecoderTest.kt new file mode 100644 index 00000000..25e63b1d --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/LiteralMultilineStringDecoderTest.kt @@ -0,0 +1,243 @@ +package com.akuleshov7.ktoml.decoders.multiline + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.exceptions.MissingRequiredPropertyException +import com.akuleshov7.ktoml.exceptions.ParseException +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class LiteralMultilineStringDecoderTest { + + @Serializable + data class SimpleString(val a: String) + + @Serializable + data class SimpleStrings(val a: String, val b: String) + + @Serializable + data class StringAndInt(val a: String, val b: Int) + + @Serializable + data class StringAndInts(val a: String, val b: Int, val c: Int) + + @Test + fun testMultilineLiteralStringDecode() { + var test = """ + a = '''abc''' + """.trimIndent() + assertEquals(SimpleString("abc"), Toml.decodeFromString(test)) + + test = """ + a = ''' + first line + second line''' + """.trimIndent() + assertEquals(SimpleString("first line\nsecond line"), Toml.decodeFromString(test)) + + test = """ + a = '''first line + second line''' + """.trimIndent() + assertEquals(SimpleString("first line\nsecond line"), Toml.decodeFromString(test)) + + test = """ + a = '''first line + + second line''' + """.trimIndent() + assertEquals(SimpleString("first line\n\nsecond line"), Toml.decodeFromString(test)) + + test = "a = '''first line\n\t \nsecond line'''" + assertEquals(SimpleString("first line\n\t \nsecond line"), Toml.decodeFromString(test)) + + test = """ + a = ''' + Roses are red + Violets are blue''' + """.trimIndent() + assertEquals(SimpleString("Roses are red\nViolets are blue"), Toml.decodeFromString(test)) + + test = """ + a = ''' + + Test''' + b = 2 + """.trimIndent() + assertEquals( + StringAndInt("\nTest", 2), + Toml.decodeFromString(test) + ) + + test = """ + a = '''Test + b = 32 + ''' + """.trimIndent() + assertEquals( + SimpleString(a = "Test\nb = 32\n"), + Toml.decodeFromString(test) + ) + } + + @Test + fun lineEndingBackslash() { + var test = """ + a = ''' + first line \ + second line \ + + third line''' + """.trimIndent() + assertEquals(SimpleString("first line second line third line"), Toml.decodeFromString(test)) + + test = """ + a = ''' + The quick brown \ + + fox jumps over \ + the lazy dog.''' + """.trimIndent() + assertEquals(SimpleString("The quick brown fox jumps over the lazy dog."), Toml.decodeFromString(test)) + + test = """ + a = '''\ + The quick brown \ + fox jumps over \ + the lazy dog.\ + ''' + """.trimIndent() + assertEquals(SimpleString("The quick brown fox jumps over the lazy dog."), Toml.decodeFromString(test)) + } + + @Test + fun testWithHashSymbol() { + val test = """ + a = ''' + Roses are red # Not a comment + # Not a comment + Violets are blue''' + """.trimIndent() + assertEquals( + SimpleString("Roses are red # Not a comment\n# Not a comment \nViolets are blue"), + Toml.decodeFromString(test) + ) + } + + @Test + fun correctQuotesInsideLiteral() { + var test = "a = '''Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"''' " + + "\nb = 2" + assertEquals( + StringAndInt("Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"", 2), + Toml.decodeFromString(test) + ) + + test = "a = ''''That,' she said, 'is still pointless.'''' " + + "\nb = 2" + assertEquals( + StringAndInt("'That,' she said, 'is still pointless.'", 2), + Toml.decodeFromString(test) + ) + + test = "a = '''Here are ten apostrophes: ''\\'''\\'''\\'' ''' " + + "\nb = 2" + assertEquals( + StringAndInt("Here are ten apostrophes: '''''''''' ", 2), + Toml.decodeFromString(test) + ) + } + + @Test + fun incorrectQuotesInside() { + val test = "a = '''Here are fifteen apostrophes: ''''''''''''''''''" + val exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + } + + @Test + fun leadingNewLines() { + // "A newline immediately following the opening delimiter will be trimmed." + var test = """ + a = ''' + + My String''' + """.trimIndent() + assertEquals( + SimpleString("\nMy String"), + Toml.decodeFromString(test) + ) + + test = """ + a = ''' + My String''' + """.trimIndent() + assertEquals( + SimpleString("My String"), + Toml.decodeFromString(test) + ) + } + + @Test + fun incorrectString() { + var test = "a = '''Test String " + var exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + + test = "a = '''Test String '" + exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + + test = "a = '''Test String \"\"\"" + exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + + test = """ + a = ''' + + Test String ' + b = "abc" + """.trimIndent() + exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + + test = """ + a = ''' + + Test String + + """.trimIndent() + exception = assertFailsWith { + Toml.decodeFromString(test) + } + assertTrue(exception.message!!.contains("Line 1")) + } + + @Test + fun betweenOtherValues() { + val test = """ + b = 2 + a = '''Test + String + ''' + c = 3 + """.trimIndent() + assertEquals( + StringAndInts("Test \nString\n", 2, 3), + Toml.decodeFromString(test) + ) + } +} \ No newline at end of file diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/MultilineArrayTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/MultilineArrayTest.kt new file mode 100644 index 00000000..035401f5 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/multiline/MultilineArrayTest.kt @@ -0,0 +1,278 @@ +package com.akuleshov7.ktoml.decoders.multiline + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.exceptions.ParseException +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class MultilineArrayTest { + + @Serializable + data class SimpleStringArray(val a: List) + + @Serializable + data class SimpleArrayWithNullableValues(val a: List) + + @Serializable + data class ClassWithImmutableList(val field: List? = null) + + @Test + fun testMultilineStringArrays() { + val expectedResult = SimpleStringArray(listOf("hey", "hi")) + var test = + """ + a = [ + "hey", + "hi" + ] + """ + assertEquals(expectedResult, Toml.decodeFromString(test)) + + test = + """ + a = ["hey", + "hi"] + """ + assertEquals(expectedResult, Toml.decodeFromString(test)) + + test = + """ + a = [ + "hey", + "hi" ] + """ + assertEquals(expectedResult, Toml.decodeFromString(test)) + + test = + """ + a = ["hey", + "hi"] + """ + assertEquals(expectedResult, Toml.decodeFromString(test)) + + test = + """ + a = ["hey", "hi" + ] + """ + assertEquals(expectedResult, Toml.decodeFromString(test)) + + test = + """ + a = ["hey", "hi", "hello" + ] + """ + assertEquals(SimpleStringArray(listOf("hey", "hi", "hello")), Toml.decodeFromString(test)) + + test = + """ + a = ["hey", "hi" + ,"hello" + ] + """ + assertEquals(SimpleStringArray(listOf("hey", "hi", "hello")), Toml.decodeFromString(test)) + + test = + """ + a = [ + ] + """ + assertEquals(SimpleStringArray(listOf()), Toml.decodeFromString(test)) + + test = + """ + a = [ + "hey=hey", + "hi=]" + ] + """ + assertEquals(SimpleStringArray(listOf("hey=hey", "hi=]")), Toml.decodeFromString(test)) + + test = + """ + a = [ + "hey=hey", + "hi=]" + ] + """ + assertEquals(SimpleStringArray(listOf("hey=hey", "hi=]")), Toml.decodeFromString(test)) + } + + @Test + fun testMultilineLongArrays() { + var testWithMultilineArray: ClassWithImmutableList = Toml.decodeFromString( + """ + field = [ + 1, + 2, + 3 + ] + """ + ) + assertEquals(listOf(1, 2, 3), testWithMultilineArray.field) + + testWithMultilineArray = Toml.decodeFromString( + """ + field = [ + 1, + null, + 3 + ] + """ + ) + assertEquals(listOf(1, null, 3), testWithMultilineArray.field) + } + + @Test + fun testMultilineArraysWithComments() { + val expectedResult = SimpleStringArray(listOf("hey", "hi")) + var test = + """ + a = [ + "hey", + "hi" #123 + ] + """ + assertEquals(expectedResult, Toml.decodeFromString(test)) + + test = + """ + #123 + #123 + a = [#123 + "hey",#123 + "hi" # 123 + ]#123 + #123 + """ + assertEquals(expectedResult, Toml.decodeFromString(test)) + + test = + """ + a = [#123 + "hey#abc", + "hi#def" # 123 + ]#123 + #123 + """ + assertEquals(SimpleStringArray(listOf("hey#abc", "hi#def")), Toml.decodeFromString(test)) + } + + @Test + fun testIncorrectMultilineArray() { + var exception = assertFailsWith { Toml.decodeFromString( + """ + field = [ + 1, + 2, + 3 + """ + ) } + assertTrue(exception.message!!.contains("Line 2")) + + exception = assertFailsWith { Toml.decodeFromString( + """ + field = [ + 1, + 2, + 3 + + """ + ) } + assertTrue(exception.message!!.contains("Line 2")) + + exception = assertFailsWith { Toml.decodeFromString( + """ + field = [ + 1, + 2, + 3 + + a = 123 + b = "abc" + """ + ) } + assertTrue(exception.message!!.contains("Line 2")) + + assertFailsWith { Toml.decodeFromString( + """ + field = [ + 1, + 2, + 3 + + a = 123 + ] + """ + ) } + assertTrue(exception.message!!.contains("Line 2")) + + assertFailsWith { Toml.decodeFromString( + """ + field = [ + 1, + 2, + 3 + + [ + a = 123 + """ + ) } + assertTrue(exception.message!!.contains("Line 2")) + + assertFailsWith { Toml.decodeFromString( + """ + field = [ + 1, + 2, + 3 + + a = [ + 1, 2 + ] + """ + ) } + assertTrue(exception.message!!.contains("Line 2")) + + assertFailsWith { Toml.decodeFromString( + """ + field = [ + 1, + 2, + 3 + + [foo] + a = 123 + """ + ) } + assertTrue(exception.message!!.contains("Line 2")) + + assertFailsWith { Toml.decodeFromString( + """ + field = [ + 1, + 2, + 3 + + a = ']' + """ + ) } + assertTrue(exception.message!!.contains("Line 2")) + } + + @Test + fun testArrayWithTrailingComma() { + val test = """ + a = [ + 1, + 2, + 3, + ] + """.trimIndent() + assertEquals(SimpleArrayWithNullableValues(listOf(1, 2, 3)), Toml.decodeFromString(test)) + } +} \ No newline at end of file diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayEncoderTest.kt new file mode 100644 index 00000000..554fe9bc --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayEncoderTest.kt @@ -0,0 +1,131 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.annotations.TomlInlineTable +import com.akuleshov7.ktoml.annotations.TomlLiteral +import com.akuleshov7.ktoml.annotations.TomlMultiline +import kotlinx.serialization.Serializable +import kotlin.test.Ignore +import kotlin.test.Test + +class ArrayEncoderTest { + @Test + fun emptyArrayTest() { + @Serializable + data class EmptyArray(val a: List = emptyList()) + + assertEncodedEquals( + value = EmptyArray(), + expectedToml = "a = [ ]" + ) + } + + @Test + fun simpleArrayTest() { + @Serializable + data class SimpleArray(val a: List = listOf(1, 2, 3)) + + assertEncodedEquals( + value = SimpleArray(), + expectedToml = "a = [ 1, 2, 3 ]" + ) + } + + @Test + fun primitiveArrayTest() { + @Serializable + data class Arrays( + val booleans: List = listOf(true, false), + val longs: List = listOf(1, 2, 3), + val doubles: List = listOf(3.14), + val basicStrings: List = listOf("a", "b", "c"), + @TomlLiteral + val literalStrings: List = listOf("\"string\"") + ) + + assertEncodedEquals( + value = Arrays(), + expectedToml = """ + booleans = [ true, false ] + longs = [ 1, 2, 3 ] + doubles = [ 3.14 ] + basicStrings = [ "a", "b", "c" ] + literalStrings = [ '"string"' ] + """.trimIndent() + ) + } + + @Test + @Ignore + fun inlineTableArrayTest() { + @Serializable + @TomlInlineTable + data class InlineTable(val index: Long) + + @Serializable + data class InlineTableArray( + @TomlMultiline + val inlineTables: List = + (0L..2L).map(::InlineTable) + ) + + assertEncodedEquals( + value = InlineTableArray(), + expectedToml = """ + inlineTables = [ + { index = 0 }, + { index = 1 }, + { index = 2 } + ] + """.trimIndent() + ) + } + + @Test + fun nestedArrayTest() { + @Serializable + data class NestedArray( + val a: List> = + listOf( + listOf(1, 2), + listOf(3, 4) + ) + ) + + assertEncodedEquals( + value = NestedArray(), + expectedToml = "a = [ [ 1, 2 ], [ 3, 4 ] ]" + ) + } + + @Test + fun arrayInTableTest() { + @Serializable + data class Table(val a: List = listOf(1, 2, 3)) + + @Serializable + data class ArrayInTable(val table: Table = Table()) + + assertEncodedEquals( + value = ArrayInTable(), + expectedToml = """ + [table] + a = [ 1, 2, 3 ] + """.trimIndent() + ) + } + + @Test + fun arrayInInlineTableTest() { + @Serializable + @TomlInlineTable + data class InlineTable(val b: List = listOf(1, 2, 3)) + + @Serializable + data class ArrayInInlineTable(val a: InlineTable = InlineTable()) + + assertEncodedEquals( + value = ArrayInInlineTable(), + expectedToml = "a = { b = [ 1, 2, 3 ] }" + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayOfTablesEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayOfTablesEncoderTest.kt new file mode 100644 index 00000000..508a4717 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayOfTablesEncoderTest.kt @@ -0,0 +1,296 @@ +package com.akuleshov7.ktoml.encoders + +import kotlinx.serialization.Serializable +import kotlin.test.Test + +class ArrayOfTablesEncoderTest { + @Test + fun simpleTableArrayTest() { + @Serializable + data class Table(val a: String, val b: String = "qwerty") + + @Serializable + data class SimpleTableArray( + val fruits: List = + listOf( + Table("apple"), + Table("banana"), + Table("plantain") + ) + ) + + assertEncodedEquals( + value = SimpleTableArray(), + expectedToml = """ + [[fruits]] + a = "apple" + b = "qwerty" + + [[fruits]] + a = "banana" + b = "qwerty" + + [[fruits]] + a = "plantain" + b = "qwerty" + """.trimIndent() + ) + } + + @Test + fun simpleTableArrayWithEmptyTest() { + @Serializable + data class Table( + val name: String? = null, + val sku: Long? = null, + val color: String? = null + ) + + @Serializable + data class SimpleTableArrayWithEmpty( + val products: List
= + listOf( + Table("Hammer", 738594937), + Table(), + Table("Nail", 284758393, "gray") + ) + ) + + assertEncodedEquals( + value = SimpleTableArrayWithEmpty(), + expectedToml = """ + [[products]] + name = "Hammer" + sku = 738594937 + + [[products]] + + [[products]] + name = "Nail" + sku = 284758393 + color = "gray" + """.trimIndent() + ) + } + + @Test + fun nestedTableArrayTest() { + @Serializable + data class InnerTable(val name: String = "granny smith") + + @Serializable + data class Table2( + val name: String = "red delicious", + val inside: List = listOf(InnerTable()) + ) + + @Serializable + data class Table1( + val varieties: List = listOf(Table2()) + ) + + @Serializable + data class NestedTableArray( + val fruits: List = listOf(Table1()) + ) + + assertEncodedEquals( + value = NestedTableArray(), + expectedToml = """ + [[fruits.varieties]] + name = "red delicious" + + [[fruits.varieties.inside]] + name = "granny smith" + """.trimIndent() + ) + } + + @Test + fun tableArrayWithNestedTableTest() { + @Serializable + data class InnerTable(val name: String = "granny smith") + + @Serializable + data class Table2( + val name: String = "red delicious", + val inside: InnerTable = InnerTable() + ) + + @Serializable + data class Table1(val varieties: List = listOf(Table2())) + + @Serializable + data class TableArrayWithNestedTable( + val fruits: List = listOf(Table1()) + ) + + assertEncodedEquals( + value = TableArrayWithNestedTable(), + expectedToml = """ + [[fruits.varieties]] + name = "red delicious" + + [fruits.varieties.inside] + name = "granny smith" + """.trimIndent() + ) + } + + @Test + fun tableArrayWithNestedTableTest2() { + @Serializable + data class InnerTable( + val color: String = "red", + val shape: String = "round" + ) + + @Serializable + data class Table(val physical: InnerTable = InnerTable()) + + @Serializable + data class TableArrayWithNestedTable( + val fruit: List
= + listOf( + Table(), + Table() + ) + ) + + assertEncodedEquals( + value = TableArrayWithNestedTable(), + expectedToml = """ + [[fruit]] + [fruit.physical] + color = "red" + shape = "round" + + [[fruit]] + [fruit.physical] + color = "red" + shape = "round" + """.trimIndent() + ) + } + + @Test + fun dottedFlatTableArrayTest() { + @Serializable + data class Table2(val name: String) + + @Serializable + data class Table1( + val varieties: List = + listOf( + Table2("red delicious"), + Table2("granny smith"), + Table2("granny smith"), + ) + ) + + @Serializable + data class FlatTableArray( + val fruits: List = listOf(Table1()) + + ) + + assertEncodedEquals( + value = FlatTableArray(), + expectedToml = """ + [[fruits.varieties]] + name = "red delicious" + + [[fruits.varieties]] + name = "granny smith" + + [[fruits.varieties]] + name = "granny smith" + """.trimIndent() + ) + } + + @Test + fun complexTableArrayTest1() { + @Serializable + data class InnerTable(val name: Long) + + @Serializable + data class Table2( + val name: Long, + val c: InnerTable + ) + + @Serializable + data class Table1( + val b: List = + listOf( + Table2(1, InnerTable(2)), + Table2(3, InnerTable(4)), + ) + ) + + @Serializable + data class ComplexTableArrays( + val a: Table1 = Table1(), + val c: List = listOf(InnerTable(5)) + ) + + assertEncodedEquals( + value = ComplexTableArrays(), + expectedToml = """ + [[a.b]] + name = 1 + + [a.b.c] + name = 2 + + [[a.b]] + name = 3 + + [a.b.c] + name = 4 + + [[c]] + name = 5 + """.trimIndent() + ) + } + + @Test + fun complexTableArrayTest2() { + @Serializable + data class InnerTable(val name: Long) + + @Serializable + data class Table2( + val c: List = + listOf( + InnerTable(2), + InnerTable(4) + ) + ) + + @Serializable + data class Table1( + val name: Long = 1, + val b: Table2 = Table2() + ) + + @Serializable + data class ComplexTable(val a: Table1 = Table1()) + + assertEncodedEquals( + value = ComplexTable(), + expectedToml = """ + [a] + name = 1 + + [[a.b.c]] + name = 2 + + [[a.b.c]] + name = 4 + """.trimIndent() + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ClassEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ClassEncoderTest.kt new file mode 100644 index 00000000..840919f7 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ClassEncoderTest.kt @@ -0,0 +1,153 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.TomlOutputConfig +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.test.Test + +class ClassEncoderTest { + @Serializable + object Object + + @Test + fun objectTest() { + @Serializable + data class File(val obj: Object = Object) + + assertEncodedEquals( + value = File(), + expectedToml = "[obj]" + ) + } + + @Test + fun keyQuotingTest() { + @Serializable + data class Table(val value: String) + + @Serializable + data class ParentTable( + @SerialName("tableⱯ") + val basicQuotedTable: Table = Table("a"), + @SerialName("\"tableꓭ\"") + val literalQuotedTable: Table = Table("b") + ) + + @Serializable + data class File( + @SerialName("key with spaces") + val basicQuotedProperty: Boolean = false, + @SerialName("keyWith\"Quotes\"") + val literalQuotedProperty: Long = 3, + val parent: ParentTable = ParentTable() + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + "key with spaces" = false + 'keyWith"Quotes"' = 3 + + [parent."tableⱯ"] + value = "a" + + [parent.'"tableꓭ"'] + value = "b" + """.trimIndent() + ) + } + + @Test + fun pairReorderingTest() { + @Serializable + data class Table( + val c: String = "value", + val tableB: Object = Object, + val d: Boolean = false + ) + + @Serializable + data class File( + val a: Long = 0, + val tableA: Table = Table(), + val b: List = listOf(1, 2, 3) + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + a = 0 + b = [ 1, 2, 3 ] + + [tableA] + c = "value" + d = false + + [tableA.tableB] + """.trimIndent() + ) + } + + @Test + fun defaultOmissionTest() { + @Serializable + data class Table( + val omitted: String = "", + val present: String + ) + + @Serializable + data class File( + val omitted: Boolean = false, + val present: Boolean, + val omittedTable: Table = Table(present = "value"), + val presentTable: Table, + ) + + assertEncodedEquals( + value = File( + present = true, + presentTable = Table(present = "value") + ), + expectedToml = """ + present = true + + [presentTable] + present = "value" + """.trimIndent(), + tomlInstance = Toml( + outputConfig = TomlOutputConfig( + ignoreDefaultValues = true + ) + ) + ) + } + + @Test + fun nullOmissionTest() { + @Serializable + data class Table( + val omitted: Long? = null, + val present: Long? = 5 + ) + + @Serializable + data class File( + val omitted: Double? = null, + val present: Double = Double.NaN, + val omittedTable: Table? = null, + val presentTable: Table? = Table() + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + present = nan + + [presentTable] + present = 5 + """.trimIndent() + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/CommentEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/CommentEncoderTest.kt new file mode 100644 index 00000000..c72e2781 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/CommentEncoderTest.kt @@ -0,0 +1,41 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.annotations.TomlComments +import kotlinx.serialization.Serializable +import kotlin.test.Ignore +import kotlin.test.Test + +class CommentEncoderTest { + @Test + @Ignore + fun commentsTest() { + @Serializable + data class Table( + @TomlComments("Comment", inline = "Comment") + val value: Boolean = true + ) + + @Serializable + data class File( + @TomlComments("Comment", inline = "Comment") + val a: Long = 0, + val b: String = "", + @TomlComments("Comment", inline = "Comment") + val table: Table = Table() + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + # Comment + a = 0 # Comment + b = "" + + # Comment + [table] # Comment + # Comment + value = true # Comment + """.trimIndent() + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/CustomSerializerTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/CustomSerializerTest.kt new file mode 100644 index 00000000..d8370cfc --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/CustomSerializerTest.kt @@ -0,0 +1,39 @@ +package com.akuleshov7.ktoml.encoders + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.encodeStructure +import kotlin.test.Test + +class CustomSerializerTest { + object SinglePropertyAsStringSerializer : KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("SingleProperty") { + element("rgb") + } + + override fun serialize(encoder: Encoder, value: SingleProperty) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, "${value.rgb - 15}") + } + } + + override fun deserialize(decoder: Decoder): SingleProperty = + throw UnsupportedOperationException() + } + + @Serializable(with = SinglePropertyAsStringSerializer::class) + data class SingleProperty(val rgb: Long = 15) + + @Test + fun singlePropertyCustomSerializerTest() { + assertEncodedEquals( + value = SingleProperty(), + expectedToml = """rgb = "0"""" + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/DateTimeEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/DateTimeEncoderTest.kt new file mode 100644 index 00000000..a963ec39 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/DateTimeEncoderTest.kt @@ -0,0 +1,37 @@ +package com.akuleshov7.ktoml.encoders + +import kotlinx.datetime.* +import kotlinx.serialization.Serializable +import kotlin.test.Test + +class DateTimeEncoderTest { + @Serializable + data class DateTimes( + val instant: Instant = default.toInstant(TimeZone.UTC), + val instantWithNanos: Instant = defaultWithNanos.toInstant(TimeZone.UTC), + val localDateTime: LocalDateTime = default, + val localDateTimeWithNanos: LocalDateTime = defaultWithNanos, + val localDate: LocalDate = default.date, + val localTime: LocalTime = default.time + ) { + companion object { + private val default = LocalDateTime(1979, 5, 27, 7, 32, 0) + private val defaultWithNanos = LocalDateTime(1979, 5, 27, 0, 32, 0, 999999000) + } + } + + @Test + fun dateTimeTest() { + assertEncodedEquals( + value = DateTimes(), + expectedToml = """ + instant = 1979-05-27T07:32:00Z + instantWithNanos = 1979-05-27T00:32:00.999999Z + localDateTime = 1979-05-27T07:32 + localDateTimeWithNanos = 1979-05-27T00:32:00.999999 + localDate = 1979-05-27 + localTime = 07:32 + """.trimIndent() + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/EncoderTesting.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/EncoderTesting.kt new file mode 100644 index 00000000..8d8a06a9 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/EncoderTesting.kt @@ -0,0 +1,13 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.Toml +import kotlinx.serialization.encodeToString +import kotlin.test.assertEquals + +inline fun assertEncodedEquals( + value: T, + expectedToml: String, + tomlInstance: Toml = Toml +) { + assertEquals(expectedToml, tomlInstance.encodeToString(value)) +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/EncodingAnnotationTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/EncodingAnnotationTest.kt new file mode 100644 index 00000000..3147c7b8 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/EncodingAnnotationTest.kt @@ -0,0 +1,286 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.annotations.* +import com.akuleshov7.ktoml.writers.IntegerRepresentation.* +import kotlinx.serialization.Serializable +import kotlin.test.Ignore +import kotlin.test.Test + +class EncodingAnnotationTest { + @Test + @Ignore + fun commentedPairTest() { + @Serializable + data class File( + @TomlComments("Single comment", inline = "") + val a: Long = 3, + @TomlComments("Comment 1", "Comment 2", inline = "") + val b: String = "test", + @TomlComments(inline = "Inline comment") + val c: Boolean = true, + @TomlComments( + "Comment 1", + "Comment 2", + inline = "Inline comment" + ) + val d: Double = Double.NaN + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + # Single comment + a = 3 + # Comment 1 + # Comment 2 + b = "test" + c = true # Inline comment + # Comment 1 + # Comment 2 + d = nan # Inline comment + """.trimIndent() + ) + } + + @Test + @Ignore + fun commentedTableTest() { + @Serializable + data class TableA(val a: String = "") + + @Serializable + data class TableB(val b: Long = 7) + + @Serializable + data class File( + @TomlComments("Comment 1", "Comment 2", inline = "") + val tableA: TableA = TableA(), + @TomlComments(inline = "Inline comment") + val tableB: TableB = TableB() + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + # Comment 1 + # Comment 2 + [tableA] + a = "" + + [tableB] # Inline comment + b = 7 + """.trimIndent() + ) + } + + @Test + fun basicInlineTableTest() + { + @Serializable + @TomlInlineTable + data class InlineTableA( + val a1: String = "test", + val a2: String = "test" + ) + + @Serializable + data class InlineTableB(val b: Boolean = false) + + @Serializable + data class File( + val a: InlineTableA = InlineTableA(), + @TomlInlineTable + val b1: InlineTableB = InlineTableB(), + //val b2: @TomlInlineTable InlineTableB = InlineTableB() + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + a = { a1 = "test", a2 = "test" } + b1 = { b = false } + """.trimIndent() + ) + } + + @Test + fun nestedInlineTableTest() { + @Serializable + data class InlineTable(val message: String) + + @Serializable + @TomlInlineTable + data class NestedInlineTable( + val inner1: InlineTable = InlineTable("a"), + val inner2: InlineTable = InlineTable("b"), + ) + + @Serializable + data class File( + val nested: NestedInlineTable = NestedInlineTable() + ) + + assertEncodedEquals( + value = File(), + expectedToml = """nested = { inner1 = { message = "a" }, inner2 = { message = "b" } }""", + tomlInstance = Toml( + outputConfig = TomlOutputConfig(explicitTables = true) + ) + ) + } + + @Test + @Ignore + fun arrayOfInlineTablesText() { + @Serializable + data class InlineTable(val value: Long) + + @Serializable + data class File( + @TomlInlineTable + val inlineTablesA: List = + (0L..2L).map(::InlineTable), + val inlineTablesB: @TomlInlineTable List = + (3L..5L).map(::InlineTable), + val inlineTablesC: List<@TomlInlineTable InlineTable> = + (6L..8L).map(::InlineTable) + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + inlineTablesA = [ { value = 0 }, { value = 1 }, { value = 2 } ] + inlineTablesB = [ { value = 3 }, { value = 4 }, { value = 5 } ] + inlineTablesC = [ { value = 6 }, { value = 7 }, { value = 8 } ] + """.trimIndent() + ) + } + + @Test + fun multilineArrayTest() { + @Serializable + data class File( + @TomlMultiline + val words: List = + listOf( + "the", "quick", "brown", + "fox", "jumps", "over", + "the", "lazy", "dog" + ), + @TomlMultiline + val fib: List = + listOf(1, 1, 2, 3, 5, 8, 13) + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + words = [ + "the", + "quick", + "brown", + "fox", + "jumps", + "over", + "the", + "lazy", + "dog" + ] + + fib = [ + 1, + 1, + 2, + 3, + 5, + 8, + 13 + ] + """.trimIndent() + ) + } + + @Test + fun integerRepresentationTest() { + @Serializable + data class File( + @TomlInteger(DECIMAL) + val dec: Long = 0, + @TomlInteger(BINARY) + val bin: Long = 2, + @TomlInteger(GROUPED) + val gro: Long = 9_999_099_009, + @TomlInteger(HEX) + val hex: Long = 4, + @TomlInteger(OCTAL) + val oct: Long = 6, + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + dec = 0 + bin = 0b10 + gro = 9_999_099_009 + hex = 0x4 + oct = 0o6 + """.trimIndent() + ) + } + + @Test + fun literalStringTest() { + @Serializable + data class File( + @TomlLiteral + val regex: String = """/[a-z-_]+|"[^"]+"/""", + //val quote: @TomlLiteral String = "\"hello!\"" + ) + + assertEncodedEquals( + value = File(), + expectedToml = """regex = '/[a-z-_]+|"[^"]+"/'""" + ) + } + + @Test + fun multilineStringTest() { + @Serializable + data class File( + @TomlMultiline + val mlTextA: String = "\n\\tMultiline\ntext!\n", + @TomlMultiline + val mlTextB: String = """ + + Text with escaped quotes ""\"\ + and line break + + """.trimIndent(), + @TomlLiteral + @TomlMultiline + val mlTextC: String = "\n\"Multiline\ntext!\"\n" + ) + + val tripleQuotes = "\"\"\"" + + assertEncodedEquals( + value = File(), + expectedToml = """ + mlTextA = $tripleQuotes + \tMultiline + text! + $tripleQuotes + mlTextB = $tripleQuotes + Text with escaped quotes ""\"\ + and line break + $tripleQuotes + mlTextC = ''' + "Multiline + text!" + ''' + """.trimIndent() + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/MapEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/MapEncoderTest.kt new file mode 100644 index 00000000..9e014098 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/MapEncoderTest.kt @@ -0,0 +1,106 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.annotations.TomlInlineTable +import com.akuleshov7.ktoml.annotations.TomlMultiline +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.test.Ignore +import kotlin.test.Test + +class MapEncoderTest { + // language=toml + private val simpleTable = """ + [map] + keyA = "a" + keyB = "b" + keyC = "c" + """.trimIndent() + + // language=toml + private val inlineTable = """ + map = { keyA = "a", keyB = "b", keyC = "c" } + """.trimIndent() + + // language=toml + private val pairArray = """ + map = [ + [ { x = 0, y = 1 }, "north" ], + [ { x = 1, y = 0 }, "east" ], + [ { x = 0, y = -1 }, "south" ], + [ { x = -1, y = 0 }, "west" ] + ] + """.trimIndent() + + private val stringMap = mapOf( + "keyA" to "a", + "keyB" to "b", + "keyC" to "c" + ) + + private val enumMap = Key.values().associateWith(Key::value) + + @Serializable + enum class Key(val value: String) { + @SerialName("keyA") KeyA("a"), + @SerialName("keyB") KeyB("b"), + @SerialName("keyC") KeyC("c") + } + + @Test + fun mapAsTableTest() { + @Serializable + data class File(val map: Map) + + assertEncodedEquals(value = File(stringMap), expectedToml = simpleTable) + } + + @Test + fun mapAsInlineTableTest() { + @Serializable + data class File( + @TomlInlineTable + val map: Map + ) + + assertEncodedEquals(value = File(stringMap), expectedToml = inlineTable) + } + + @Test + fun enumMapAsTableTest() { + @Serializable + data class File(val map: Map) + + assertEncodedEquals(value = File(enumMap), expectedToml = simpleTable) + } + + @Test + fun enumMapAsInlineTableTest() { + @Serializable + data class File( + @TomlInlineTable + val map: Map + ) + + assertEncodedEquals(value = File(enumMap), expectedToml = inlineTable) + } + + @Test + @Ignore + fun arbitraryKeyMapTest() { + @Serializable + data class Point(val x: Int, val y: Int) + + @Serializable + data class File( + @TomlMultiline + val map: Map = mapOf( + Point(0, 1) to "north", + Point(1, 0) to "east", + Point(0, -1) to "south", + Point(-1, 0) to "west" + ) + ) + + assertEncodedEquals(value = File(), expectedToml = pairArray) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PolymorphicEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PolymorphicEncoderTest.kt new file mode 100644 index 00000000..5dd88cc0 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PolymorphicEncoderTest.kt @@ -0,0 +1,109 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.annotations.TomlInlineTable +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.serialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.test.Test + +@OptIn(ExperimentalSerializationApi::class) +class PolymorphicEncoderTest { + @Serializable + sealed class Parent { + @Serializable(ChildA.InPlaceSerializer::class) + @SerialName("childA") + class ChildA(val list: List = listOf(1, 2, 3)) : Parent() { + object InPlaceSerializer : KSerializer { + override val descriptor = SerialDescriptor("childA", serialDescriptor>()) + + override fun serialize(encoder: Encoder, value: ChildA) { + serializer>().serialize(encoder, value.list) + } + + override fun deserialize(decoder: Decoder): Nothing = throw IllegalStateException() + } + } + + @Serializable(ChildB.InPlaceSerializer::class) + @SerialName("childB") + class ChildB(val value: String = "string") : Parent() { + object InPlaceSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("childB", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: ChildB) { + encoder.encodeString(value.value) + } + + override fun deserialize(decoder: Decoder): Nothing = throw IllegalStateException() + } + } + + @Serializable(ChildC.InPlaceSerializer::class) + @SerialName("childC") + class ChildC(val value: Enum = Enum.A) : Parent() { + @Serializable + enum class Enum { A } + + object InPlaceSerializer : KSerializer { + override val descriptor = SerialDescriptor("childC", serialDescriptor()) + + override fun serialize(encoder: Encoder, value: ChildC) { + encoder.encodeEnum(serialDescriptor(), value.value.ordinal) + } + + override fun deserialize(decoder: Decoder): Nothing = throw IllegalStateException() + } + } + } + + @Test + fun sealedTableArrayTest() { + @Serializable + class File( + val polymorphicTables: List = listOf( + Parent.ChildA(), + Parent.ChildB(), + Parent.ChildC() + ) + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + [[polymorphicTables]] + type = "childA" + value = [ 1, 2, 3 ] + + [[polymorphicTables]] + type = "childB" + value = "string" + + [[polymorphicTables]] + type = "childC" + value = "A" + """.trimIndent() + ) + } + + @Test + fun sealedArrayElementTest() { + @Serializable + class File( + @TomlInlineTable + val polymorphicArray: List = listOf( + Parent.ChildA(), + Parent.ChildB(), + Parent.ChildC() + ) + ) + + assertEncodedEquals( + value = File(), + expectedToml = """polymorphicArray = [ [ "childA", [ 1, 2, 3 ] ], [ "childB", "string" ], [ "childC", "A" ] ]""" + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PrimitiveEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PrimitiveEncoderTest.kt new file mode 100644 index 00000000..a2e1d3a9 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PrimitiveEncoderTest.kt @@ -0,0 +1,96 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.annotations.TomlLiteral +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.test.Test + +class PrimitiveEncoderTest { + @Serializable + enum class Greeting { + @SerialName("hello") Hello + } + + @Test + fun primitivesTest() { + @Serializable + data class File( + val enabled: Boolean = true, + val pi: Double = 3.14, + val count: Long = 3, + val port: Int = 8080, + val greeting: String = "hello", + val enumGreeting: Greeting = Greeting.Hello, + @TomlLiteral + val path: String = """C:\some\path\""" + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + enabled = true + pi = 3.14 + count = 3 + port = 8080 + greeting = "hello" + enumGreeting = "hello" + path = 'C:\some\path\' + """.trimIndent() + ) + } + + @Test + fun stringEscapeTest() { + @Serializable + data class File( + val escapeString: String? = null, + @TomlLiteral + val literalEscapeString: String? = null + ) + + val tab = '\t' + + assertEncodedEquals( + value = File("\"hello world\""), + expectedToml = """escapeString = "\"hello world\""""" + ) + + assertEncodedEquals( + value = File("hello \b\t\n\u000C\r world"), + expectedToml = """escapeString = "hello \b$tab\n\f\r world"""" + ) + + assertEncodedEquals( + value = File("hello \u0000 world"), + expectedToml = """escapeString = "hello \u0000 world"""" + ) + + assertEncodedEquals( + value = File("""hello\world"""), + expectedToml = """escapeString = "hello\\world"""" + ) + + assertEncodedEquals( + value = File("""hello \Uffffffff world"""), + expectedToml = """escapeString = "hello \Uffffffff world"""" + ) + + assertEncodedEquals( + value = File(literalEscapeString = "'quotes'"), + expectedToml = """literalEscapeString = '\'quotes\''""" + ) + } + + @Test + fun jsWholeDoubleRegression() { + @Serializable + data class File( + val wholeNumberDouble: Double = 3.0 + ) + + assertEncodedEquals( + value = File(), + expectedToml = "wholeNumberDouble = 3.0" + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ReadMeExampleTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ReadMeExampleTest.kt new file mode 100644 index 00000000..70595733 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ReadMeExampleTest.kt @@ -0,0 +1,89 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.annotations.TomlInlineTable +import com.akuleshov7.ktoml.annotations.TomlLiteral +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.test.Test + +class ReadMeExampleTest { + @Serializable + data class MyClass( + val someBooleanProperty: Boolean, + val table1: Table1, + val table2: Table2, + @SerialName("gradle-libs-like-property") + val kotlinJvm: GradlePlugin + ) + + @Serializable + data class Table1( + // nullable values, from toml you can pass null/nil/empty value to this kind of a field + val property1: Long?, + // please note, that according to the specification of toml integer values should be represented with Long + val property2: Long, + // no need to pass this value as it has the default value and is NOT REQUIRED + val property3: Long = 5 + ) + + @Serializable + data class Table2( + val someNumber: Long, + @SerialName("akuleshov7.com") + val inlineTable: NestedTable, + val otherNumber: Double + ) + + @Serializable + data class NestedTable( + @TomlLiteral + val name: String, + @SerialName("configurationList") + val overriddenName: List + ) + + @Serializable + @TomlInlineTable + data class GradlePlugin(val id: String, val version: Version) + + @Serializable + data class Version(val ref: String) + + @Test + fun readMeExampleTest() { + assertEncodedEquals( + value = MyClass( + true, + Table1(null, 6), + Table2( + 5, + NestedTable("this is a \"literal\" string", listOf("a", "b", "c", null)), + 5.56 + ), + GradlePlugin("org.jetbrains.kotlin.jvm", Version("kotlin")) + ), + expectedToml = """ + someBooleanProperty = true + gradle-libs-like-property = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } + + [table1] + property1 = null + property2 = 6 + property3 = 5 + + [table2] + someNumber = 5 + otherNumber = 5.56 + + [table2."akuleshov7.com"] + name = 'this is a "literal" string' + configurationList = [ "a", "b", "c", null ] + """.trimIndent(), + tomlInstance = Toml( + outputConfig = TomlOutputConfig(ignoreNullValues = false) + ) + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/TomlDocsEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/TomlDocsEncoderTest.kt new file mode 100644 index 00000000..12cc6138 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/TomlDocsEncoderTest.kt @@ -0,0 +1,162 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.TomlIndentation +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.annotations.TomlInlineTable +import com.akuleshov7.ktoml.annotations.TomlLiteral +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toInstant +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.descriptors.buildSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.encodeCollection +import kotlin.test.Ignore +import kotlin.test.Test + +class TomlDocsEncoderTest { + @Serializable + data class ReadMe( + val title: String = "TOML Example", + val owner: Owner = Owner(), + val database: Database = Database(), + val servers: Servers = Servers() + ) + + @Serializable + data class Owner( + val name: String = "Tom Preston-Werner", + val dob: Instant = + LocalDateTime(1979, 5, 27, 15, 32, 0) + .toInstant(TimeZone.UTC) + ) + + @Serializable + data class Database( + val enabled: Boolean = true, + val ports: List = listOf(8000, 8001, 8002), + @Serializable(with = DataSerializer::class) + val data: List> = listOf(listOf("delta", "phi"), listOf(3.14)), + @SerialName("temp_targets") + @TomlInlineTable + val tempTargets: Map = mapOf("cpu" to 79.5, "case" to 72.0) + ) { + // Serializing this as a hard-coded list instead of making Any @Polymorphic + // because these would be serialized as [ "kotlin.", ] rather + // than as primitives. Making an exception for primitives in the encoder + // works, but could make types difficult to resolve on deserialization. + @OptIn(ExperimentalSerializationApi::class) + object DataSerializer : KSerializer>> { + @OptIn(InternalSerializationApi::class) + override val descriptor = buildSerialDescriptor("data", StructureKind.LIST) { + element>("0") + element>("1") + } + + @Suppress("UNCHECKED_CAST") + override fun serialize(encoder: Encoder, value: List>) { + encoder.encodeCollection(descriptor, 2) { + encodeSerializableElement(descriptor, 0, serializer<_>(), value[0] as List) + encodeSerializableElement(descriptor, 1, serializer<_>(), value[1] as List) + } + } + + override fun deserialize(decoder: Decoder): List> { + throw IllegalStateException() + } + } + } + + @Serializable + data class Servers( + val alpha: Entry = Entry("10.0.0.1", "frontend"), + val beta: Entry = Entry("10.0.0.2", "backend") + ) { + @Serializable + data class Entry(val ip: String, val role: String) + } + + @Test + fun readMeTest() { + assertEncodedEquals( + value = ReadMe(), + expectedToml = """ + title = "TOML Example" + + [owner] + name = "Tom Preston-Werner" + dob = 1979-05-27T15:32:00Z + + [database] + enabled = true + ports = [ 8000, 8001, 8002 ] + data = [ [ "delta", "phi" ], [ 3.14 ] ] + temp_targets = { cpu = 79.5, case = 72.0 } + + [servers] + [servers.alpha] + ip = "10.0.0.1" + role = "frontend" + + [servers.beta] + ip = "10.0.0.2" + role = "backend" + """.trimIndent(), + tomlInstance = Toml( + outputConfig = TomlOutputConfig.compliant( + indentation = TomlIndentation.NONE, + explicitTables = true + ) + ) + ) + } + + @Test + fun basicStringTest() { + @Serializable + data class File( + val str1: String = "I'm a string.", + val str2: String = "You can \"quote\" me.", + val str3: String = "Name\tJos\u00E9\nLoc\tSF." + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + str1 = "I'm a string." + str2 = "You can \"quote\" me." + str3 = "Name José\nLoc SF." + """.trimIndent() + ) + } + + @Test + fun literalStringTest() { + @Serializable + data class File( + @TomlLiteral + val winpath: String = """C:\Users\nodejs\templates""", + @TomlLiteral + val winpath2: String = """\\ServerX\admin${'$'}\system32\""", + @TomlLiteral + val quoted: String = """Tom "Dubs" Preston-Werner""", + @TomlLiteral + val regex: String = """<\i\c*\s*>""" + ) + + assertEncodedEquals( + value = File(), + expectedToml = """ + winpath = 'C:\Users\nodejs\templates' + winpath2 = '\\ServerX\admin${'$'}\system32\' + quoted = 'Tom "Dubs" Preston-Werner' + regex = '<\i\c*\s*>' + """.trimIndent() + ) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ArraysOfTablesTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ArraysOfTablesTest.kt index a954a13f..545e8df7 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ArraysOfTablesTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ArraysOfTablesTest.kt @@ -1,7 +1,7 @@ package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.Toml.Default.tomlParser -import com.akuleshov7.ktoml.tree.TomlArrayOfTablesElement +import com.akuleshov7.ktoml.tree.nodes.TomlArrayOfTablesElement import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -30,14 +30,14 @@ class ArraysOfTablesTest { | - TomlFile (rootNode) | - TomlArrayOfTables ([[fruits]]) | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (a=apple) - | - TomlKeyValuePrimitive (b=qwerty) + | - TomlKeyValuePrimitive (a="apple") + | - TomlKeyValuePrimitive (b="qwerty") | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (a=banana) - | - TomlKeyValuePrimitive (b=qwerty) + | - TomlKeyValuePrimitive (a="banana") + | - TomlKeyValuePrimitive (b="qwerty") | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (a=plantain) - | - TomlKeyValuePrimitive (b=qwerty) + | - TomlKeyValuePrimitive (a="plantain") + | - TomlKeyValuePrimitive (b="qwerty") | """.trimMargin(), parsedToml.prettyStr() @@ -68,13 +68,13 @@ class ArraysOfTablesTest { | - TomlFile (rootNode) | - TomlArrayOfTables ([[products]]) | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (name=Hammer) + | - TomlKeyValuePrimitive (name="Hammer") | - TomlKeyValuePrimitive (sku=738594937) | - TomlArrayOfTablesElement (technical_node) | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (name=Nail) + | - TomlKeyValuePrimitive (name="Nail") | - TomlKeyValuePrimitive (sku=284758393) - | - TomlKeyValuePrimitive (color=gray) + | - TomlKeyValuePrimitive (color="gray") | """.trimMargin(), parsedToml.prettyStr() @@ -98,12 +98,12 @@ class ArraysOfTablesTest { """ | - TomlFile (rootNode) | - TomlArrayOfTables ([[fruits]]) - | - TomlArrayOfTables ([[fruits.varieties]] ) + | - TomlArrayOfTables ([[fruits.varieties]]) | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (name=red delicious) + | - TomlKeyValuePrimitive (name="red delicious") | - TomlArrayOfTables ([[fruits.varieties.inside]]) | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (name=granny smith) + | - TomlKeyValuePrimitive (name="granny smith") | """.trimMargin(), parsedToml.prettyStr() @@ -127,11 +127,11 @@ class ArraysOfTablesTest { """ | - TomlFile (rootNode) | - TomlArrayOfTables ([[fruits]]) - | - TomlArrayOfTables ([[fruits.varieties]] ) + | - TomlArrayOfTables ([[fruits.varieties]]) | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (name=red delicious) + | - TomlKeyValuePrimitive (name="red delicious") | - TomlTablePrimitive ([fruits.varieties.inside]) - | - TomlKeyValuePrimitive (name=granny smith) + | - TomlKeyValuePrimitive (name="granny smith") | """.trimMargin(), parsedToml.prettyStr() @@ -196,7 +196,7 @@ class ArraysOfTablesTest { assertEquals( """ | - TomlFile (rootNode) - | - TomlArrayOfTables ([[a]] ) + | - TomlArrayOfTables ([[a]]) | - TomlArrayOfTablesElement (technical_node) | - TomlArrayOfTables ([[a.b]]) | - TomlArrayOfTablesElement (technical_node) @@ -225,7 +225,7 @@ class ArraysOfTablesTest { assertEquals( """ | - TomlFile (rootNode) - | - TomlArrayOfTables ([[a]] ) + | - TomlArrayOfTables ([[a]]) | - TomlArrayOfTablesElement (technical_node) | - TomlArrayOfTables ([[a.b]]) | - TomlArrayOfTablesElement (technical_node) @@ -281,24 +281,24 @@ class ArraysOfTablesTest { | - TomlFile (rootNode) | - TomlArrayOfTables ([[fruits]]) | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (name=apple) + | - TomlKeyValuePrimitive (name="apple") | - TomlTablePrimitive ([fruits.physical]) - | - TomlKeyValuePrimitive (color=red) - | - TomlKeyValuePrimitive (shape=round) + | - TomlKeyValuePrimitive (color="red") + | - TomlKeyValuePrimitive (shape="round") | - TomlTablePrimitive ([fruits.physical]) | - TomlTablePrimitive ([fruits.physical.inside]) - | - TomlKeyValuePrimitive (color=red) - | - TomlKeyValuePrimitive (shape=round) - | - TomlArrayOfTables ([[fruits.varieties]] ) + | - TomlKeyValuePrimitive (color="red") + | - TomlKeyValuePrimitive (shape="round") + | - TomlArrayOfTables ([[fruits.varieties]]) | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (name=red delicious) + | - TomlKeyValuePrimitive (name="red delicious") | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (name=granny smith) + | - TomlKeyValuePrimitive (name="granny smith") | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (name=banana) + | - TomlKeyValuePrimitive (name="banana") | - TomlArrayOfTables ([[fruits.varieties]]) | - TomlArrayOfTablesElement (technical_node) - | - TomlKeyValuePrimitive (name=plantain) + | - TomlKeyValuePrimitive (name="plantain") | - TomlTablePrimitive ([vegetables]) | - TomlKeyValuePrimitive (outSideOfArray=true) | @@ -399,16 +399,16 @@ class ArraysOfTablesTest { | - TomlArrayOfTables ([[a]]) | - TomlArrayOfTables ([[a.b]]) | - TomlArrayOfTablesElement (technical_node) - | - TomlArrayOfTables ( [[a.b.c]]) + | - TomlArrayOfTables ([[a.b.c]]) | - TomlArrayOfTablesElement (technical_node) | - TomlArrayOfTablesElement (technical_node) | - TomlKeyValuePrimitive (a=1) | - TomlArrayOfTables ([[a.b.c.d]]) | - TomlArrayOfTables ([[a.b.c.d.e]]) - | - TomlArrayOfTables ( [[a.b.c.d.e.f]]) + | - TomlArrayOfTables ([[a.b.c.d.e.f]]) | - TomlArrayOfTablesElement (technical_node) | - TomlArrayOfTablesElement (technical_node) - | - TomlTablePrimitive ( [a.b.c]) + | - TomlTablePrimitive ([a.b.c]) | - TomlStubEmptyNode (technical_node) | """.trimMargin(), parsedToml.prettyStr() @@ -477,13 +477,13 @@ class ArraysOfTablesTest { | - TomlFile (rootNode) | - TomlArrayOfTables ([[a]]) | - TomlArrayOfTablesElement (technical_node) - | - TomlTablePrimitive ( [a.b]) + | - TomlTablePrimitive ([a.b]) | - TomlStubEmptyNode (technical_node) | - TomlArrayOfTablesElement (technical_node) - | - TomlTablePrimitive ( [a.b]) + | - TomlTablePrimitive ([a.b]) | - TomlStubEmptyNode (technical_node) | - TomlArrayOfTablesElement (technical_node) - | - TomlTablePrimitive ( [a.b]) + | - TomlTablePrimitive ([a.b]) | - TomlStubEmptyNode (technical_node) | """.trimMargin(), @@ -589,13 +589,13 @@ class ArraysOfTablesTest { | - TomlFile (rootNode) | - TomlArrayOfTables ([[fruit]]) | - TomlArrayOfTablesElement (technical_node) - | - TomlTablePrimitive ([fruit.physical] ) - | - TomlKeyValuePrimitive (color=red) - | - TomlKeyValuePrimitive (shape=round) + | - TomlTablePrimitive ([fruit.physical]) + | - TomlKeyValuePrimitive (color="red") + | - TomlKeyValuePrimitive (shape="round") | - TomlArrayOfTablesElement (technical_node) - | - TomlTablePrimitive ([fruit.physical] ) - | - TomlKeyValuePrimitive (color=red) - | - TomlKeyValuePrimitive (shape=round) + | - TomlTablePrimitive ([fruit.physical]) + | - TomlKeyValuePrimitive (color="red") + | - TomlKeyValuePrimitive (shape="round") | """.trimMargin(), parsedToml.prettyStr()) diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/CommentsParsing.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/CommentsParsing.kt index d9d4cc11..2333d078 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/CommentsParsing.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/CommentsParsing.kt @@ -1,7 +1,10 @@ package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals class CommentsParsing { @Test @@ -16,5 +19,37 @@ class CommentsParsing { """.trimIndent() val parsedToml = Toml.tomlParser.parseString(string) parsedToml.prettyPrint() + + val tableA = parsedToml.findTableInAstByName("a")!! + val tableB = tableA.findTableInAstByName("a.b")?.getFirstChild()!! + + val pairA = + tableA.children + .first { it is TomlKeyValuePrimitive } + + assertContentEquals( + listOf("comment 1"), + tableA.comments + ) + + assertEquals( + "comment 2", + tableA.inlineComment + ) + + assertContentEquals( + listOf("comment 3"), + pairA.comments + ) + + assertEquals( + "comment 4", + pairA.inlineComment + ) + + assertEquals( + "comment 5", + tableB.inlineComment + ) } } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/CommonParserTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/CommonParserTest.kt index 88124e43..6a8a82b6 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/CommonParserTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/CommonParserTest.kt @@ -1,7 +1,8 @@ package com.akuleshov7.ktoml.parsers -import com.akuleshov7.ktoml.tree.TomlArray -import com.akuleshov7.ktoml.tree.TomlBasicString +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlArray +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlBasicString import kotlin.test.Test import kotlin.test.assertEquals @@ -12,7 +13,8 @@ class CommonParserTest { var midLevelValues = 0 var lowLevelValues = 0 - TomlArray("[[\"a\", [\"b\"]], \"c\", \"d\"]", 0).parse().forEach { + @Suppress("UNCHECKED_CAST") + (TomlArray("[[\"a\", [\"b\"]], \"c\", \"d\"]", 0, TomlInputConfig()).content as List).forEach { when (it) { is TomlBasicString -> highLevelValues++ is List<*> -> it.forEach { diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/DottedKeyParserTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/DottedKeyParserTest.kt index 6ca01903..c338ebc9 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/DottedKeyParserTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/DottedKeyParserTest.kt @@ -2,9 +2,9 @@ package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.exceptions.ParseException -import com.akuleshov7.ktoml.tree.TomlFile -import com.akuleshov7.ktoml.tree.TomlKey -import com.akuleshov7.ktoml.tree.TomlKeyValuePrimitive +import com.akuleshov7.ktoml.tree.nodes.TomlFile +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive +import com.akuleshov7.ktoml.tree.nodes.pairs.keys.TomlKey import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -13,33 +13,33 @@ class DottedKeyParserTest { @Test fun positiveParsingTest() { var test = TomlKey("\"a.b.c\"", 0) - assertEquals("a.b.c", test.content) + assertEquals("a.b.c", test.last()) assertEquals(false, test.isDotted) test = TomlKey("\"a.b.c\".b.c", 0) - assertEquals("c", test.content) + assertEquals("c", test.last()) assertEquals(listOf("\"a.b.c\"", "b", "c"), test.keyParts) assertEquals(true, test.isDotted) test = TomlKey("\"a\".b.c", 0) - assertEquals("c", test.content) + assertEquals("c", test.last()) assertEquals(listOf("\"a\"", "b", "c"), test.keyParts) assertEquals(true, test.isDotted) test = TomlKey("\" a \"", 0) - assertEquals("a", test.content) + assertEquals("a", test.last()) assertEquals(false, test.isDotted) test = TomlKey("a.b.c", 0) - assertEquals("c", test.content) + assertEquals("c", test.last()) assertEquals(true, test.isDotted) test = TomlKey("a.\" b .c \"", 0) - assertEquals("b .c", test.content) + assertEquals("b .c", test.last()) assertEquals(true, test.isDotted) test = TomlKey("a . b . c ", 0) - assertEquals("c", test.content) + assertEquals("c", test.last()) assertEquals(true, test.isDotted) assertFailsWith { TomlKey("SPACE AND SPACE", 0) } @@ -48,13 +48,13 @@ class DottedKeyParserTest { @Test fun createTable() { var test = TomlKeyValuePrimitive(Pair("google.com","5"), 0).createTomlTableFromDottedKey(TomlFile()) - assertEquals("google", test.fullTableName) + assertEquals("google", test.fullTableKey.toString()) test = TomlKeyValuePrimitive(Pair("a.b.c.d", "5"), 0).createTomlTableFromDottedKey(TomlFile()) - assertEquals("a.b.c", test.fullTableName) + assertEquals("a.b.c", test.fullTableKey.toString()) val testKeyValue = TomlKeyValuePrimitive(Pair("a.b.c", "5"), 0) - assertEquals("c", testKeyValue.key.content) + assertEquals("c", testKeyValue.key.last()) } @Test diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/StringUtilsTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/StringUtilsTest.kt new file mode 100644 index 00000000..4716f236 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/StringUtilsTest.kt @@ -0,0 +1,49 @@ +package com.akuleshov7.ktoml.parsers + +import kotlin.test.Test +import kotlin.test.assertEquals + +class StringUtilsTest { + @Test + fun testForTakeBeforeComment() { + var lineWithoutComment = "test_key = \"test_value\" # \" some comment".takeBeforeComment(false) + assertEquals("test_key = \"test_value\"", lineWithoutComment) + + lineWithoutComment = "key = \"\"\"value\"\"\" # \"".takeBeforeComment(false) + assertEquals("key = \"\"\"value\"\"\"", lineWithoutComment) + + lineWithoutComment = "key = 123 # \"\"\"abc".takeBeforeComment(false) + assertEquals("key = 123", lineWithoutComment) + + lineWithoutComment = "key = \"ab\\\"#cdef\"#123".takeBeforeComment(false) + assertEquals("key = \"ab\\\"#cdef\"", lineWithoutComment) + + lineWithoutComment = " \t#123".takeBeforeComment(false) + assertEquals("", lineWithoutComment) + + lineWithoutComment = "key = \"ab\'c\" # ".takeBeforeComment(false) + assertEquals("key = \"ab\'c\"", lineWithoutComment) + + lineWithoutComment = """ + a = 'C:\some\path\' #\abc + """.trimIndent().takeBeforeComment(true) + assertEquals("""a = 'C:\some\path\'""", lineWithoutComment) + } + + @Test + fun testForTrimComment() { + var comment = "a = \"here#hash\" # my comment".trimComment(false) + assertEquals("my comment", comment) + + comment = "a = \"here#\\\"hash\" # my comment".trimComment(false) + assertEquals("my comment", comment) + + comment = " # my comment".trimComment(false) + assertEquals("my comment", comment) + + comment = """ + a = 'C:\some\path\' #\abc + """.trimIndent().trimComment(true) + assertEquals("\\abc", comment) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TableFinder.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TableFinder.kt index 17cc0281..535b549e 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TableFinder.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TableFinder.kt @@ -28,6 +28,6 @@ class TableFinder { """.trimIndent() val parsedToml = Toml.tomlParser.parseString(string) - assertEquals("a.b.d.e.f", findPrimitiveTableInAstByName(listOf(parsedToml), "a.b.d.e.f")?.fullTableName) + assertEquals("a.b.d.e.f", findPrimitiveTableInAstByName(listOf(parsedToml), "a.b.d.e.f")?.fullTableKey.toString()) } -} \ No newline at end of file +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TomlTableTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TomlTableTest.kt index 9dcd12e3..b410fba2 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TomlTableTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TomlTableTest.kt @@ -1,7 +1,7 @@ package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.tree.TomlTablePrimitive +import com.akuleshov7.ktoml.tree.nodes.TomlTablePrimitive import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals @@ -27,7 +27,6 @@ class TomlTableTest { } @Test - @Ignore fun nestedTomlTable() { val string = """ [a] @@ -43,7 +42,8 @@ class TomlTableTest { assertEquals(""" | - TomlFile (rootNode) | - TomlTablePrimitive ([a]) - | - TomlTablePrimitive ( [a.b]) + | - TomlStubEmptyNode (technical_node) + | - TomlTablePrimitive ([a.b]) | - TomlStubEmptyNode (technical_node) | - TomlKeyValuePrimitive (c=3) | @@ -116,6 +116,6 @@ class TomlTableTest { @Test fun createSimpleTomlTable() { val table = TomlTablePrimitive("[abcd]", 0) - assertEquals(table.fullTableName, "abcd") + assertEquals(table.fullTableKey.toString(), "abcd") } } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ValueParserTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ValueParserTest.kt index 48c8da7a..c7313c2d 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ValueParserTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ValueParserTest.kt @@ -1,8 +1,10 @@ package com.akuleshov7.ktoml.parsers -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig import com.akuleshov7.ktoml.exceptions.ParseException -import com.akuleshov7.ktoml.tree.* +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive +import com.akuleshov7.ktoml.tree.nodes.pairs.values.* +import com.akuleshov7.ktoml.tree.nodes.splitKeyValue import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -33,7 +35,7 @@ class ValueParserTest { fun nullParsingTest() { testTomlValue("a" to "null", NodeType.NULL) assertFailsWith { - testTomlValue("a" to "null", NodeType.NULL, TomlConfig(allowNullValues = false)) + testTomlValue("a" to "null", NodeType.NULL, TomlInputConfig(allowNullValues = false)) } } @@ -110,6 +112,12 @@ class ValueParserTest { assertEquals("1 = 2", TomlKeyValuePrimitive(pairTest, 0).value.content) } + @Test + fun symbolsAfterComment() { + val keyValue = "test_key = \"test_value\" # \" some comment".splitKeyValue(0) + assertEquals("test_value", TomlKeyValuePrimitive(keyValue, 0).value.content) + } + @Test fun parsingIssueValue() { assertFailsWith { " = false".splitKeyValue(0) } @@ -154,7 +162,7 @@ fun getNodeType(v: TomlValue): NodeType = when (v) { fun testTomlValue( keyValuePair: Pair, expectedType: NodeType, - config: TomlConfig = TomlConfig() + config: TomlInputConfig = TomlInputConfig() ) { - assertEquals(expectedType, getNodeType(TomlKeyValuePrimitive(keyValuePair, 0, config).value)) + assertEquals(expectedType, getNodeType(TomlKeyValuePrimitive(keyValuePair, 0, config = config).value)) } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/ArrayOfTablesWriteTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/ArrayOfTablesWriteTest.kt index 1be25575..b66e96a3 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/ArrayOfTablesWriteTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/ArrayOfTablesWriteTest.kt @@ -1,7 +1,8 @@ package com.akuleshov7.ktoml.writers import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig import kotlin.test.Test import kotlin.test.assertEquals @@ -143,18 +144,19 @@ class ArrayOfTablesWriteTest { fun testTableArray( expected: String, - config: TomlConfig = TomlConfig() + inputConfig: TomlInputConfig = TomlInputConfig(), + outputConfig: TomlOutputConfig = TomlOutputConfig() ) { - val file = Toml(config).tomlParser.parseString(expected) + val file = Toml(inputConfig).tomlParser.parseString(expected) file.prettyPrint() assertEquals( expected, buildString(expected.length) { - val emitter = TomlStringEmitter(this, config) + val emitter = TomlStringEmitter(this, outputConfig) - file.write(emitter, config) + file.write(emitter, outputConfig) } ) } \ No newline at end of file diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/CommentWriteTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/CommentWriteTest.kt new file mode 100644 index 00000000..a7e2d435 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/CommentWriteTest.kt @@ -0,0 +1,27 @@ +package com.akuleshov7.ktoml.writers + +import kotlin.test.Test + +class CommentWriteTest { + @Test + fun commentWriteTest() { + val toml = """ + # Comment + # Comment + x = 0 # Comment + + # Comment + [a] # Comment + b = [ ] # Comment + + # Comment + [a.c] + d = 1 # Comment + + # Comment + [[e]] # Comment + """.trimIndent() + + testTable(toml) + } +} \ No newline at end of file diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/KeyValueWriteTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/KeyValueWriteTest.kt index 351cc8ee..6323a83b 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/KeyValueWriteTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/KeyValueWriteTest.kt @@ -1,10 +1,12 @@ package com.akuleshov7.ktoml.writers -import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.tree.TomlInlineTable -import com.akuleshov7.ktoml.tree.TomlKeyValueArray -import com.akuleshov7.ktoml.tree.TomlKeyValuePrimitive -import com.akuleshov7.ktoml.tree.TomlNode +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.tree.nodes.TomlInlineTable +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValueArray +import com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive +import com.akuleshov7.ktoml.tree.nodes.TomlNode +import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlArray import kotlin.test.Test import kotlin.test.assertEquals @@ -50,8 +52,8 @@ class KeyValueWriteTest { testTomlPrimitivePair(dottedKey to "1979-05-27") // Null - testTomlPrimitivePair(bareKey to "null", TomlConfig(allowNullValues = true)) - testTomlPrimitivePair(dottedKey to "null", TomlConfig(allowNullValues = true)) + testTomlPrimitivePair(bareKey to "null", TomlInputConfig(allowNullValues = true)) + testTomlPrimitivePair(dottedKey to "null", TomlInputConfig(allowNullValues = true)) } @Test @@ -93,47 +95,50 @@ class KeyValueWriteTest { fun testTomlPrimitivePair( pair: Pair, - config: TomlConfig = TomlConfig() + inputConfig: TomlInputConfig = TomlInputConfig(), + outputConfig: TomlOutputConfig = TomlOutputConfig() ) = testTomlPair( - TomlKeyValuePrimitive(pair, 0, config), + TomlKeyValuePrimitive(pair, 0, config = inputConfig), expectedString = "${pair.first} = ${pair.second}", - config, - multiline = false + outputConfig ) fun testTomlArrayPair( pair: Pair, multiline: Boolean, - config: TomlConfig = TomlConfig(), + inputConfig: TomlInputConfig = TomlInputConfig(), + outputConfig: TomlOutputConfig = TomlOutputConfig() ) = testTomlPair( - TomlKeyValueArray(pair, 0, config), + TomlKeyValueArray(pair, 0, config = inputConfig).also { + val array = it.value as TomlArray + + array.multiline = multiline + }, expectedString = "${pair.first} = ${pair.second}", - config, - multiline + outputConfig ) fun testTomlInlineTablePair( pair: Pair, - config: TomlConfig = TomlConfig(), + inputConfig: TomlInputConfig = TomlInputConfig(), + outputConfig: TomlOutputConfig = TomlOutputConfig() ) = testTomlPair( - TomlInlineTable(pair, 0, config), + TomlInlineTable(pair, 0, config = inputConfig), expectedString = "${pair.first} = ${pair.second}", - config, - multiline = false + outputConfig ) fun testTomlPair( pair: TomlNode, expectedString: String, - config: TomlConfig, - multiline: Boolean + config: TomlOutputConfig = TomlOutputConfig() ) { assertEquals( expectedString, actual = buildString { val emitter = TomlStringEmitter(this, config) - pair.write(emitter, config, multiline) + pair.write(emitter, config) } ) } \ No newline at end of file diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/TableWriteTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/TableWriteTest.kt index 394306cf..dcf0cfc6 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/TableWriteTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/TableWriteTest.kt @@ -1,7 +1,8 @@ package com.akuleshov7.ktoml.writers import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig import kotlin.test.Test import kotlin.test.assertEquals @@ -80,18 +81,19 @@ class TableWriteTest { fun testTable( expected: String, - config: TomlConfig = TomlConfig() + inputConfig: TomlInputConfig = TomlInputConfig(), + outputConfig: TomlOutputConfig = TomlOutputConfig() ) { - val file = Toml(config).tomlParser.parseString(expected) + val file = Toml(inputConfig, outputConfig).tomlParser.parseString(expected) file.prettyPrint() assertEquals( expected, buildString(expected.length) { - val emitter = TomlStringEmitter(this, config) + val emitter = TomlStringEmitter(this, outputConfig) - file.write(emitter, config) + file.write(emitter, outputConfig) } ) } \ No newline at end of file diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/ValueWriteTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/ValueWriteTest.kt index ccf1949d..2f86aaf6 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/ValueWriteTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/writers/ValueWriteTest.kt @@ -1,11 +1,12 @@ package com.akuleshov7.ktoml.writers -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlOutputConfig import com.akuleshov7.ktoml.exceptions.TomlWritingException -import com.akuleshov7.ktoml.tree.* +import com.akuleshov7.ktoml.tree.nodes.pairs.values.* import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime import kotlin.Double.Companion.NEGATIVE_INFINITY import kotlin.Double.Companion.NaN import kotlin.Double.Companion.POSITIVE_INFINITY @@ -18,19 +19,16 @@ class PrimitiveValueWriteTest { @Test fun literalStringWriteTest() { // Valid, normal case - testTomlValue(TomlLiteralString("literal \tstring" as Any, 0), "'literal \tstring'") + testTomlValue(TomlLiteralString("literal \tstring" as Any), "'literal \tstring'") // Control characters rejection - testTomlValueFailure(TomlLiteralString("control \u0000\bchars" as Any, 0)) - - // Escape rejection - testTomlValueFailure(TomlLiteralString("\\\" escapes\\n \\\"" as Any, 0)) + testTomlValueFailure(TomlLiteralString("control \u0000\bchars" as Any)) // Escaped single quotes - val disallowQuotes = TomlConfig(allowEscapedQuotesInLiteralStrings = false) + val disallowQuotes = TomlOutputConfig(allowEscapedQuotesInLiteralStrings = false) - val escapedSingleQuotes = TomlLiteralString("'escaped quotes'" as Any, 0) + val escapedSingleQuotes = TomlLiteralString("'escaped quotes'" as Any) testTomlValueFailure(escapedSingleQuotes, disallowQuotes) @@ -39,48 +37,64 @@ class PrimitiveValueWriteTest { @Test fun basicStringWriteTest() { - testTomlValue(TomlBasicString("hello world" as Any, 0), "\"hello world\"") + testTomlValue(TomlBasicString("hello world" as Any), "\"hello world\"") // Control character escaping - testTomlValue(TomlBasicString("hello \b\t\n\u000C\r world" as Any, 0), "\"hello \\b\t\\n\\f\\r world\"") - testTomlValue(TomlBasicString("hello \u0000 world" as Any, 0), "\"hello \\u0000 world\"") + testTomlValue(TomlBasicString("hello \b\t\n\u000C\r world" as Any), "\"hello \\b\t\\n\\f\\r world\"") + testTomlValue(TomlBasicString("hello \u0000 world" as Any), "\"hello \\u0000 world\"") // Backslash escaping - testTomlValue(TomlBasicString("""hello\world""" as Any, 0), """"hello\\world"""") - testTomlValue(TomlBasicString("""hello\\\ world""" as Any, 0), """"hello\\\\ world"""") - testTomlValue(TomlBasicString("""hello\b\t\n\\\f\r world""" as Any, 0), """"hello\b\t\n\\\f\r world"""") - testTomlValue(TomlBasicString("""hello\u0000\\\Uffffffff world""" as Any, 0), """"hello\u0000\\\Uffffffff world"""") + testTomlValue(TomlBasicString("""hello\world""" as Any), """"hello\\world"""") + testTomlValue(TomlBasicString("""hello\\\ world""" as Any), """"hello\\\\ world"""") + testTomlValue(TomlBasicString("""hello\b\t\n\\\f\r world""" as Any), """"hello\b\t\n\\\f\r world"""") + testTomlValue(TomlBasicString("""hello\u0000\\\Uffffffff world""" as Any), """"hello\u0000\\\Uffffffff world"""") } - @Suppress("COMMENTED_CODE") @Test fun integerWriteTest() { // Decimal - testTomlValue(TomlLong(1234567L, 0), "1234567") - testTomlValue(TomlLong(-1234567L, 0), "-1234567") + testTomlValue(TomlLong(1234567L), "1234567") + testTomlValue(TomlLong(-1234567L), "-1234567") + testTomlValue(TomlLong(Long.MIN_VALUE), "${Long.MIN_VALUE}") // Hex - //testTomlValue(TomlLong(0xdeadc0de, 0, IntegerRepresentation.HEX), "0xdeadc0de") + testTomlValue(TomlLong(0xdeadc0deL, IntegerRepresentation.HEX), "0xdeadc0de") + testTomlValue(TomlLong(-0xdeadc0deL, IntegerRepresentation.HEX), "-0xdeadc0de") + testTomlValue(TomlLong(Long.MIN_VALUE, IntegerRepresentation.HEX), "-0x" + Long.MIN_VALUE.toString(16).trimStart('-')) // Binary - //testTomlValue(TomlLong(0b10000000, 0, IntegerRepresentation.BINARY), "0b10000000") + testTomlValue(TomlLong(0b10000000L, IntegerRepresentation.BINARY), "0b10000000") + testTomlValue(TomlLong(-0b10000000L, IntegerRepresentation.BINARY), "-0b10000000") + testTomlValue(TomlLong(Long.MIN_VALUE, IntegerRepresentation.BINARY), "-0b" + Long.MIN_VALUE.toString(2).trimStart('-')) // Octal - //testTomlValue(TomlLong(0x1FF, 0, IntegerRepresentation.OCTAL), "0o777") + testTomlValue(TomlLong(0x1FFL, IntegerRepresentation.OCTAL), "0o777") + testTomlValue(TomlLong(-0x1FFL, IntegerRepresentation.OCTAL), "-0o777") + testTomlValue(TomlLong(Long.MIN_VALUE, IntegerRepresentation.OCTAL), "-0o" + Long.MIN_VALUE.toString(8).trimStart('-')) + + // Grouped + testTomlValue(TomlLong(1234567L, IntegerRepresentation.GROUPED), "1_234_567") + testTomlValue(TomlLong(-1234567L, IntegerRepresentation.GROUPED), "-1_234_567") + testTomlValue(TomlLong(Long.MIN_VALUE, IntegerRepresentation.GROUPED), "-9_223_372_036_854_775_808") } @Test fun floatWriteTest() { - testTomlValue(TomlDouble(PI, 0), "$PI") - testTomlValue(TomlDouble(NaN, 0), "nan") - testTomlValue(TomlDouble(POSITIVE_INFINITY, 0), "inf") - testTomlValue(TomlDouble(NEGATIVE_INFINITY, 0), "-inf") + testTomlValue(TomlDouble(PI), "$PI") + testTomlValue(TomlDouble(NaN), "nan") + testTomlValue(TomlDouble(POSITIVE_INFINITY), "inf") + testTomlValue(TomlDouble(NEGATIVE_INFINITY), "-inf") + } + + @Test + fun wholeNumberFloatRegressionTest() { + testTomlValue(TomlDouble(3.0), "3.0") } @Test fun booleanWriteTest() { - testTomlValue(TomlBoolean(true, 0), "true") - testTomlValue(TomlBoolean(false, 0), "false") + testTomlValue(TomlBoolean(true), "true") + testTomlValue(TomlBoolean(false), "false") } @Test @@ -88,31 +102,31 @@ class PrimitiveValueWriteTest { val instant = "1979-05-27T07:32:00Z" val localDt = "1979-05-27T07:32" val localD = "1979-05-27" + val localT = "07:32:32" - testTomlValue(TomlDateTime(Instant.parse(instant), 0), instant) - testTomlValue(TomlDateTime(LocalDateTime.parse(localDt), 0), localDt) - testTomlValue(TomlDateTime(LocalDate.parse(localD), 0), localD) + testTomlValue(TomlDateTime(Instant.parse(instant)), instant) + testTomlValue(TomlDateTime(LocalDateTime.parse(localDt)), localDt) + testTomlValue(TomlDateTime(LocalDate.parse(localD)), localD) + testTomlValue(TomlDateTime(LocalTime.parse(localT)), localT) } @Test - fun nullWriteTest() = testTomlValue(TomlNull(0), "null") + fun nullWriteTest() = testTomlValue(TomlNull(), "null") @Test fun arrayWriteTest() { + val innerArray = TomlArray( + listOf( + TomlDouble(3.14) + ) + ) + val array = TomlArray( listOf( - TomlLong(1L, 0), - TomlBasicString("string" as Any, 0), - TomlArray( - listOf( - TomlDouble(3.14, 0) - ), - "", - 0 - ) - ), - "", - 0 + TomlLong(1L), + TomlBasicString("string" as Any), + innerArray + ) ) // Inline @@ -124,6 +138,9 @@ class PrimitiveValueWriteTest { // Multiline + innerArray.multiline = true + array.multiline = true + testTomlValue( array, """ @@ -134,8 +151,7 @@ class PrimitiveValueWriteTest { 3.14 ] ] - """.trimIndent(), - multiline = true + """.trimIndent() ) } } @@ -143,26 +159,25 @@ class PrimitiveValueWriteTest { fun testTomlValue( value: TomlValue, expectedString: String, - config: TomlConfig = TomlConfig(), - multiline: Boolean = false + config: TomlOutputConfig = TomlOutputConfig() ) { assertEquals( expectedString, actual = buildString { val emitter = TomlStringEmitter(this, config) - value.write(emitter, config, multiline) + value.write(emitter, config) } ) } fun testTomlValueFailure( value: TomlValue, - config: TomlConfig = TomlConfig() + config: TomlOutputConfig = TomlOutputConfig() ) { assertFailsWith { val emitter = TomlStringEmitter(StringBuilder(), config) value.write(emitter, config) } -} \ No newline at end of file +} diff --git a/ktoml-core/src/iosMain/kotlin/com/akuleshov7/ktoml/utils/UtilsIos.kt b/ktoml-core/src/iosMain/kotlin/com/akuleshov7/ktoml/utils/UtilsIos.kt new file mode 100644 index 00000000..458f07f2 --- /dev/null +++ b/ktoml-core/src/iosMain/kotlin/com/akuleshov7/ktoml/utils/UtilsIos.kt @@ -0,0 +1,15 @@ +/** + * Specific implementation for utilities + */ + +package com.akuleshov7.ktoml.utils + +@Suppress("MAGIC_NUMBER") +internal actual fun StringBuilder.appendCodePointCompat(codePoint: Int): StringBuilder = when (codePoint) { + in 0 until Char.MIN_SUPPLEMENTARY_CODE_POINT -> append(codePoint.toChar()) + in Char.MIN_SUPPLEMENTARY_CODE_POINT..Char.MAX_CODE_POINT -> { + append(Char.MIN_HIGH_SURROGATE + ((codePoint - 0x10000) shr 10)) + append(Char.MIN_LOW_SURROGATE + (codePoint and 0x3ff)) + } + else -> throw IllegalArgumentException() +} diff --git a/ktoml-core/src/iosSimulatorArm64Main/kotlin/com/akuleshov7/ktoml/utils/UtilsSimulator.kt b/ktoml-core/src/iosSimulatorArm64Main/kotlin/com/akuleshov7/ktoml/utils/UtilsSimulator.kt new file mode 100644 index 00000000..458f07f2 --- /dev/null +++ b/ktoml-core/src/iosSimulatorArm64Main/kotlin/com/akuleshov7/ktoml/utils/UtilsSimulator.kt @@ -0,0 +1,15 @@ +/** + * Specific implementation for utilities + */ + +package com.akuleshov7.ktoml.utils + +@Suppress("MAGIC_NUMBER") +internal actual fun StringBuilder.appendCodePointCompat(codePoint: Int): StringBuilder = when (codePoint) { + in 0 until Char.MIN_SUPPLEMENTARY_CODE_POINT -> append(codePoint.toChar()) + in Char.MIN_SUPPLEMENTARY_CODE_POINT..Char.MAX_CODE_POINT -> { + append(Char.MIN_HIGH_SURROGATE + ((codePoint - 0x10000) shr 10)) + append(Char.MIN_LOW_SURROGATE + (codePoint and 0x3ff)) + } + else -> throw IllegalArgumentException() +} diff --git a/ktoml-core/src/macosArm64Main/kotlin/com/akuleshov7/ktoml/utils/UtilsMacM1.kt b/ktoml-core/src/macosArm64Main/kotlin/com/akuleshov7/ktoml/utils/UtilsMacM1.kt new file mode 100644 index 00000000..d21d8fce --- /dev/null +++ b/ktoml-core/src/macosArm64Main/kotlin/com/akuleshov7/ktoml/utils/UtilsMacM1.kt @@ -0,0 +1,17 @@ +/** + * Specific implementation for utilities + */ + +@file:Suppress("PACKAGE_NAME_INCORRECT_PATH") + +package com.akuleshov7.ktoml.utils + +@Suppress("MAGIC_NUMBER") +internal actual fun StringBuilder.appendCodePointCompat(codePoint: Int): StringBuilder = when (codePoint) { + in 0 until Char.MIN_SUPPLEMENTARY_CODE_POINT -> append(codePoint.toChar()) + in Char.MIN_SUPPLEMENTARY_CODE_POINT..Char.MAX_CODE_POINT -> { + append(Char.MIN_HIGH_SURROGATE + ((codePoint - 0x10000) shr 10)) + append(Char.MIN_LOW_SURROGATE + (codePoint and 0x3ff)) + } + else -> throw IllegalArgumentException() +} diff --git a/ktoml-file/build.gradle.kts b/ktoml-file/build.gradle.kts index f09bbb09..a29c24ab 100644 --- a/ktoml-file/build.gradle.kts +++ b/ktoml-file/build.gradle.kts @@ -9,57 +9,33 @@ plugins { kotlin { explicitApi() + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(8)) + } + jvm { compilations.all { - kotlinOptions { - jvmTarget = "11" - } + kotlinOptions.jvmTarget = "1.8" } } mingwX64() linuxX64() macosX64() + macosArm64() + ios() sourceSets { all { languageSettings.optIn("kotlin.RequiresOptIn") } - val linuxX64Main by getting { - dependencies { - implementation("com.squareup.okio:okio:${Versions.OKIO}") - implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") - } - } - - val mingwX64Main by getting { - dependencies { - implementation("com.squareup.okio:okio:${Versions.OKIO}") - implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") - } - } - - val macosX64Main by getting { - dependencies { - implementation("com.squareup.okio:okio:${Versions.OKIO}") - implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") - } - } - - val jvmMain by getting { - dependencies { - implementation("com.squareup.okio:okio:${Versions.OKIO}") - implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") - } - } - val commonMain by getting { dependencies { implementation("com.squareup.okio:okio:${Versions.OKIO}") implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") - implementation(project(":ktoml-source")) implementation(project(":ktoml-core")) + implementation(project(":ktoml-source")) } } @@ -89,3 +65,11 @@ configurePublishing() tasks.withType { useJUnitPlatform() } + +// ios tests on github are behaving differently than locally - as github moves resources to a different directory +// so, as it is not critical, skipping them +tasks.withType { + if (this.name.contains("ios")) { + this.enabled = false + } +} 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 7e529bf2..50c6428f 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,12 +4,8 @@ package com.akuleshov7.ktoml.file -import okio.BufferedSink -import okio.FileNotFoundException -import okio.FileSystem +import okio.* import okio.Path.Companion.toPath -import okio.Source -import okio.buffer /** * Simple file reading with okio (returning a list with strings) @@ -37,12 +33,12 @@ internal fun getFileSource(filePath: String): Source { * @return A [BufferedSink] writing to the specified [tomlFile] path. * @throws FileNotFoundException */ -internal fun openFileForWrite(tomlFile: String): BufferedSink { +internal fun openFileForWrite(filePath: String): BufferedSink { try { - val tomlPath = tomlFile.toPath() + val tomlPath = filePath.toPath() return getOsSpecificFileSystem().sink(tomlPath).buffer() } catch (e: FileNotFoundException) { - throw FileNotFoundException("Not able to find TOML file on path $tomlFile: ${e.message}") + throw FileNotFoundException("Not able to find TOML file on path $filePath: ${e.message}") } } diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt index 58a5dd40..93d1eb4f 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt @@ -1,11 +1,13 @@ + package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig import com.akuleshov7.ktoml.source.TomlSourceReader import kotlin.native.concurrent.ThreadLocal import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer @@ -15,11 +17,15 @@ import kotlinx.serialization.serializer * that is used to serialize/deserialize TOML file or string * @property serializersModule */ -@OptIn(ExperimentalSerializationApi::class) -public open class TomlFileReader( - config: TomlConfig = TomlConfig(), - override val serializersModule: SerializersModule = EmptySerializersModule -) : TomlSourceReader(config, serializersModule) { +public open class TomlFileReader public constructor( + inputConfig: TomlInputConfig = TomlInputConfig(), + outputConfig: TomlOutputConfig = TomlOutputConfig(), + serializersModule: SerializersModule = EmptySerializersModule() +) : TomlSourceReader( + inputConfig, + outputConfig, + serializersModule +) { /** * Simple deserializer of a file that contains toml. Reading file with okio native library * @@ -32,16 +38,6 @@ public open class TomlFileReader( tomlFilePath: String, ): T = decodeFromSource(deserializer, getFileSource(tomlFilePath)) - /** - * Simple deserializer of a file that contains toml. Reading file with okio native library - * - * @param tomlFilePath path to the file where toml is stored - * @return deserialized object of type T - */ - public inline fun decodeFromFile( - tomlFilePath: String, - ): T = decodeFromFile(serializersModule.serializer(), tomlFilePath) - /** * Partial deserializer of a file that contains toml. Reading file with okio native library. * Will deserialize only the part presented under the tomlTableName table. @@ -84,5 +80,8 @@ public open class TomlFileReader( * ThreadLocal annotation is used here for caching. */ @ThreadLocal - public companion object Default : TomlFileReader(TomlConfig()) + public companion object Default : TomlFileReader( + inputConfig = TomlInputConfig(), + outputConfig = TomlOutputConfig() + ) } 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 ff63014a..e09c06c3 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,9 @@ package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.tree.TomlFile +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import com.akuleshov7.ktoml.encoders.TomlMainEncoder import okio.use @@ -16,22 +18,27 @@ import kotlinx.serialization.modules.SerializersModule * @property serializersModule */ @OptIn(ExperimentalSerializationApi::class) -public open class TomlFileWriter( - private val config: TomlConfig = TomlConfig(), - override val serializersModule: SerializersModule = EmptySerializersModule -) : Toml(config, serializersModule) { +public open class TomlFileWriter : Toml { + public constructor( + inputConfig: TomlInputConfig = TomlInputConfig(), + outputConfig: TomlOutputConfig = TomlOutputConfig(), + serializersModule: SerializersModule = EmptySerializersModule(), + ) : super( + inputConfig, + outputConfig, + serializersModule + ) + public fun encodeToFile( serializer: SerializationStrategy, value: T, tomlFilePath: String ) { - val fileTree = TomlFile(config) - - // Todo: Write an encoder implementation. + val fileTree = TomlMainEncoder.encode(serializer, value) TomlSinkEmitter( openFileForWrite(tomlFilePath), - config + outputConfig ).use { tomlWriter.write(fileTree, it) } 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 index a97da8a0..3775966a 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkEmitter.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkEmitter.kt @@ -2,7 +2,7 @@ package com.akuleshov7.ktoml.file -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlOutputConfig import com.akuleshov7.ktoml.writers.TomlEmitter import okio.BufferedSink import okio.Closeable @@ -12,7 +12,7 @@ import okio.Closeable */ internal class TomlSinkEmitter( private val sink: BufferedSink, - config: TomlConfig + config: TomlOutputConfig ) : TomlEmitter(config), Closeable { override fun emit(fragment: String): TomlEmitter { sink.writeUtf8(fragment) diff --git a/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/Regression189.kt b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/Regression189.kt new file mode 100644 index 00000000..9c3a874b --- /dev/null +++ b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/Regression189.kt @@ -0,0 +1,33 @@ +package com.akuleshov7.ktoml.file + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.serializer +import kotlin.test.Test +import kotlin.test.assertEquals + +class Regression189 { + @Serializable + data class ServerSettings( + val email: String, + val url: String, + val deviceId: String, + val bearerToken: String?, + val refreshToken: String?, + ) + + @ExperimentalSerializationApi + @Test + fun regressionCastTest() { + val file = "src/commonTest/resources/regression_189.toml" + val parsedResult = TomlFileReader.decodeFromFile(serializer(), file) + assertEquals(ServerSettings( + "test5", + "http://localhost:8080", + "50694ed7-a93f-4713-9e55-4d512ce2e4db", + "a8DkRGThvz13cmVubFdgX0CsoLfAtXcBvyxiKCPY34FEt3UDmPBkMKFRk4iKRuRp", + "20NSBgKB2B9C2u2toAuiPqlaZgfEWJ4m50562YK9w575SNt31CWrjcpwqeiDCYhZ"), + parsedResult + ) + } +} \ No newline at end of file diff --git a/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt index 59d2b911..9a9fa2aa 100644 --- a/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt +++ b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt @@ -1,9 +1,9 @@ package com.akuleshov7.ktoml.file -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig import com.akuleshov7.ktoml.parsers.TomlParser import com.akuleshov7.ktoml.source.useLines -import com.akuleshov7.ktoml.tree.TomlTablePrimitive +import com.akuleshov7.ktoml.tree.nodes.TomlTablePrimitive import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable import kotlinx.serialization.serializer @@ -85,13 +85,15 @@ class TomlFileParserTest { // ==== reading from file val test = MyTableTest(A(Ab(InnerTest("Undefined")), InnerTest("Undefined")), D(InnerTest("Undefined"))) assertEquals(test, TomlFileReader.decodeFromFile(serializer(), file)) + // ==== checking how table discovery works val parsedResult = getFileSource(file).useLines { lines -> - TomlParser(TomlConfig()).parseStringsToTomlTree(lines, TomlConfig()) + TomlParser(TomlInputConfig()).parseStringsToTomlTree(lines, TomlInputConfig()) } + assertEquals( listOf("a", "a.b.c", "a.d", "d", "d.a"), - parsedResult.getRealTomlTables().map { it.fullTableName }) + parsedResult.getRealTomlTables().map { it.fullTableKey.toString() }) } @Serializable @@ -99,8 +101,8 @@ class TomlFileParserTest { @ExperimentalSerializationApi @Test - fun regressionCast2Test() { - val file = "src/commonTest/resources/class_cast_regression2.toml" + fun regressionCastTest() { + val file = "src/commonTest/resources/class_cast_regression.toml" val parsedResult = TomlFileReader.decodeFromFile(serializer(), file) assertEquals(RegressionTest(null, 1, 2, null), parsedResult) } @@ -108,7 +110,7 @@ class TomlFileParserTest { @ExperimentalSerializationApi @Test fun regressionPartialTest() { - val file = "src/commonTest/resources/class_cast_regression2.toml" + val file = "src/commonTest/resources/class_cast_regression.toml" val parsedResult = TomlFileReader.decodeFromFile(serializer(), file) assertEquals(RegressionTest(null, 1, 2, null), parsedResult) } @@ -202,12 +204,12 @@ class TomlFileParserTest { assertEquals( listOf("owner", "database"), getFileSource(file).useLines { lines -> - TomlParser(TomlConfig()) - .parseStringsToTomlTree(lines, TomlConfig()) + TomlParser(TomlInputConfig()) + .parseStringsToTomlTree(lines, TomlInputConfig()) .children .filterIsInstance() .filter { !it.isSynthetic } - .map { it.fullTableName } + .map { it.fullTableKey.toString() } } ) } diff --git a/ktoml-file/src/commonTest/resources/class_cast_regression2.toml b/ktoml-file/src/commonTest/resources/class_cast_regression.toml similarity index 100% rename from ktoml-file/src/commonTest/resources/class_cast_regression2.toml rename to ktoml-file/src/commonTest/resources/class_cast_regression.toml diff --git a/ktoml-file/src/commonTest/resources/class_cast_regression1.toml b/ktoml-file/src/commonTest/resources/class_cast_regression1.toml deleted file mode 100644 index c3c67301..00000000 --- a/ktoml-file/src/commonTest/resources/class_cast_regression1.toml +++ /dev/null @@ -1,4 +0,0 @@ -a = 1 -b = null -c = 2 -d = null diff --git a/ktoml-file/src/commonTest/resources/regression_189.toml b/ktoml-file/src/commonTest/resources/regression_189.toml new file mode 100644 index 00000000..2929d953 --- /dev/null +++ b/ktoml-file/src/commonTest/resources/regression_189.toml @@ -0,0 +1,5 @@ +email = "test5" +url = "http://localhost:8080" +deviceId = "50694ed7-a93f-4713-9e55-4d512ce2e4db" +bearerToken = "a8DkRGThvz13cmVubFdgX0CsoLfAtXcBvyxiKCPY34FEt3UDmPBkMKFRk4iKRuRp" +refreshToken = "20NSBgKB2B9C2u2toAuiPqlaZgfEWJ4m50562YK9w575SNt31CWrjcpwqeiDCYhZ" diff --git a/ktoml-file/src/commonTest/resources/simple_example.toml b/ktoml-file/src/commonTest/resources/simple_example.toml index cd84341a..6b8da607 100644 --- a/ktoml-file/src/commonTest/resources/simple_example.toml +++ b/ktoml-file/src/commonTest/resources/simple_example.toml @@ -3,7 +3,7 @@ title = "TOML \"Example\"" [owner] -name = "Tom Preston-Werner" +name = "Tom Pr\u0065ston-Werner" dob = "1979-05-27T07:32:00-08:00" # First class dates [database] diff --git a/ktoml-file/src/iosMain/kotlin/com/akuleshov7/ktoml/file/FileUtilsIos.kt b/ktoml-file/src/iosMain/kotlin/com/akuleshov7/ktoml/file/FileUtilsIos.kt new file mode 100644 index 00000000..6904d387 --- /dev/null +++ b/ktoml-file/src/iosMain/kotlin/com/akuleshov7/ktoml/file/FileUtilsIos.kt @@ -0,0 +1,14 @@ +/** + * File utils to read files using okio + */ + +package com.akuleshov7.ktoml.file + +import okio.FileSystem + +/** + * Implementation for getting proper file system to read files with okio + * + * @return proper FileSystem + */ +internal actual fun getOsSpecificFileSystem(): FileSystem = FileSystem.SYSTEM diff --git a/ktoml-file/src/macosArm64Main/kotlin/com/akuleshov7/ktoml/file/FileUtilsMacM1.kt b/ktoml-file/src/macosArm64Main/kotlin/com/akuleshov7/ktoml/file/FileUtilsMacM1.kt new file mode 100644 index 00000000..511eeeae --- /dev/null +++ b/ktoml-file/src/macosArm64Main/kotlin/com/akuleshov7/ktoml/file/FileUtilsMacM1.kt @@ -0,0 +1,16 @@ +/** + * File utils to read files using okio + */ + +@file:Suppress("PACKAGE_NAME_INCORRECT_PATH") + +package com.akuleshov7.ktoml.file + +import okio.FileSystem + +/** + * Implementation for getting proper file system to read files with okio + * + * @return proper FileSystem + */ +internal actual fun getOsSpecificFileSystem(): FileSystem = FileSystem.SYSTEM diff --git a/ktoml-source/build.gradle.kts b/ktoml-source/build.gradle.kts index 2a93d6e3..2c0f37a4 100644 --- a/ktoml-source/build.gradle.kts +++ b/ktoml-source/build.gradle.kts @@ -9,53 +9,32 @@ plugins { kotlin { explicitApi() + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(8)) + } + + // building jvm task only on windows jvm { compilations.all { - kotlinOptions { - jvmTarget = "11" - } + kotlinOptions.jvmTarget = "1.8" } } mingwX64() linuxX64() macosX64() + macosArm64() + ios() + iosSimulatorArm64() sourceSets { all { languageSettings.optIn("kotlin.RequiresOptIn") } - val linuxX64Main by getting { - dependencies { - implementation("com.squareup.okio:okio:${Versions.OKIO}") - implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") - } - } - - val mingwX64Main by getting { - dependencies { - implementation("com.squareup.okio:okio:${Versions.OKIO}") - implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") - } - } - - val macosX64Main by getting { - dependencies { - implementation("com.squareup.okio:okio:${Versions.OKIO}") - implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") - } - } - - val jvmMain by getting { - dependencies { - implementation("com.squareup.okio:okio:${Versions.OKIO}") - implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") - } - } - val commonMain by getting { dependencies { + api("org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.SERIALIZATION}") implementation("com.squareup.okio:okio:${Versions.OKIO}") implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN}") implementation(project(":ktoml-core")) diff --git a/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt index e1917d7b..6bb7064d 100644 --- a/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt +++ b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt @@ -1,7 +1,8 @@ package com.akuleshov7.ktoml.source import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig import okio.Source @@ -18,9 +19,10 @@ import kotlinx.serialization.serializer */ @OptIn(ExperimentalSerializationApi::class) public open class TomlSourceReader( - private val config: TomlConfig = TomlConfig(), + inputConfig: TomlInputConfig = TomlInputConfig(), + outputConfig: TomlOutputConfig = TomlOutputConfig(), override val serializersModule: SerializersModule = EmptySerializersModule -) : Toml(config, serializersModule) { +) : Toml(inputConfig, outputConfig, serializersModule) { /** * Simple deserializer of a source that contains toml. * @@ -31,7 +33,7 @@ public open class TomlSourceReader( public fun decodeFromSource( deserializer: DeserializationStrategy, source: Source, - ): T = source.useLines { lines -> decodeFromString(deserializer, lines, config) } + ): T = source.useLines { lines -> decodeFromString(deserializer, lines, inputConfig) } /** * Simple deserializer of a source that contains toml. @@ -59,7 +61,7 @@ public open class TomlSourceReader( source: Source, tomlTableName: String, ): T = source.useLines { lines -> - partiallyDecodeFromLines(deserializer, lines, tomlTableName, config) + partiallyDecodeFromLines(deserializer, lines, tomlTableName, inputConfig) } /** @@ -85,5 +87,5 @@ public open class TomlSourceReader( * ThreadLocal annotation is used here for caching. */ @ThreadLocal - public companion object Default : TomlSourceReader(TomlConfig()) + public companion object Default : TomlSourceReader(TomlInputConfig()) } diff --git a/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt b/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt index 1052f231..51841855 100644 --- a/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt +++ b/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt @@ -1,9 +1,9 @@ package com.akuleshov7.ktoml.source import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.TomlInputConfig import com.akuleshov7.ktoml.parsers.TomlParser -import com.akuleshov7.ktoml.tree.TomlTablePrimitive +import com.akuleshov7.ktoml.tree.nodes.TomlTablePrimitive import okio.source import org.junit.jupiter.api.Test @@ -46,7 +46,7 @@ class StreamTests { // ==== checking how table discovery works val parsedResult = getTestDataStream("complex_toml_tables.toml").source().useLines { lines -> - TomlParser(TomlConfig()).parseStringsToTomlTree(lines, TomlConfig()) + TomlParser(TomlInputConfig()).parseStringsToTomlTree(lines, TomlInputConfig()) } assertEquals( listOf("a", "a.b.c", "a.d", "d", "d.a"), @@ -124,8 +124,8 @@ class StreamTests { assertEquals( listOf("owner", "database"), getTestDataStream("simple_example.toml").source().useLines { lines -> - TomlParser(TomlConfig()) - .parseStringsToTomlTree(lines, TomlConfig()) + TomlParser(TomlInputConfig()) + .parseStringsToTomlTree(lines, TomlInputConfig()) .children .filterIsInstance() .filter { !it.isSynthetic } diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..952fddf7 --- /dev/null +++ b/renovate.json @@ -0,0 +1,37 @@ +{ + "enabled": true, + "dependencyDashboard": true, + "schedule": [ + "before 4am on Monday" + ], + "lockFileMaintenance": { + "enabled": true + }, + "packageRules": [ + { + "managers": ["github-actions"], + "groupName": "all github actions", + "groupSlug": "all-github-actions" + }, + { + "managers": ["gradle"], + "matchPackagePatterns": [ + "*" + ], + "matchUpdateTypes": [ + "minor", + "patch" + ], + "groupName": "all non-major dependencies (except core Kotlin)", + "groupSlug": "all-minor-patch" + }, + { + "managers": ["gradle"], + "matchPackagePatterns": [ + "^org\\.jetbrains\\.kotlin[.:]" + ], + "groupName": "Kotlin core dependencies", + "groupSlug": "core-kotlin" + } + ] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..5fa7a043 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,987 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.10.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" + integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/helper-validator-identifier@^7.18.6": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda" + integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@1.4.14": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.18" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" + integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + +"@js-joda/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" + integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== + +"@rollup/plugin-commonjs@^21.0.1": + version "21.1.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-21.1.0.tgz#45576d7b47609af2db87f55a6d4b46e44fc3a553" + integrity sha512-6ZtHx3VHIp2ReNNDxHjuUml6ur+WcQ28N1yHgCQwsbNkQg2suhxGMDQGJOn/KuDxKtd1xuZP5xSTwBA4GQ8hbA== + dependencies: + "@rollup/pluginutils" "^3.1.0" + commondir "^1.0.1" + estree-walker "^2.0.1" + glob "^7.1.6" + is-reference "^1.2.1" + magic-string "^0.25.7" + resolve "^1.17.0" + +"@rollup/plugin-node-resolve@^13.1.3": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz#da1c5c5ce8316cef96a2f823d111c1e4e498801c" + integrity sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw== + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + deepmerge "^4.2.2" + is-builtin-module "^3.1.0" + is-module "^1.0.0" + resolve "^1.19.0" + +"@rollup/plugin-typescript@^8.3.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.5.0.tgz#7ea11599a15b0a30fa7ea69ce3b791d41b862515" + integrity sha512-wMv1/scv0m/rXx21wD2IsBbJFba8wGF3ErJIr6IKRfRj49S85Lszbxb4DCo8iILpluTjk2GAAu9CoZt4G3ppgQ== + dependencies: + "@rollup/pluginutils" "^3.1.0" + resolve "^1.17.0" + +"@rollup/pluginutils@^3.0.9", "@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + +"@types/estree@*": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + +"@types/node@*": + version "18.15.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" + integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + +"@types/node@^12.12.14": + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== + +"@types/resolve@1.17.1": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" + integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== + dependencies: + "@types/node" "*" + +"@ungap/promise-all-settled@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" + integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== + +acorn@^8.5.0: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + +camelcase@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +debug@4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +decode-uri-component@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +dukat@0.5.8-rc.4: + version "0.5.8-rc.4" + resolved "https://registry.yarnpkg.com/dukat/-/dukat-0.5.8-rc.4.tgz#90384dcb50b14c26f0e99dae92b2dea44f5fce21" + integrity sha512-ZnMt6DGBjlVgK2uQamXfd7uP/AxH7RqI0BL9GLrrJb2gKdDxvJChWy+M9AQEaL+7/6TmxzJxFOsRiInY9oGWTA== + dependencies: + google-protobuf "3.12.2" + typescript "3.9.5" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + +estree-walker@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +format-util@1.0.5, format-util@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" + integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.3, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +google-protobuf@3.12.2: + version "3.12.2" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.12.2.tgz#50ce9f9b6281235724eb243d6a83e969a2176e53" + integrity sha512-4CZhpuRr1d6HjlyrxoXoocoGFnRYgKULgMtikMddA9ztRyYR59Aondv2FioyxWVamRo0rF2XpYawkTCBEQOSkA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-builtin-module@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + +is-core-module@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-reference@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" + integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== + dependencies: + "@types/estree" "*" + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +jest-worker@^26.2.1: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +log-symbols@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +magic-string@^0.25.7: + version "0.25.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +mocha@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" + integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.2.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + nanoid "3.3.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +resolve@^1.17.0, resolve@^1.19.0: + version "1.22.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + dependencies: + is-core-module "^2.11.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup-plugin-sourcemaps@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz#bf93913ffe056e414419607f1d02780d7ece84ed" + integrity sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw== + dependencies: + "@rollup/pluginutils" "^3.0.9" + source-map-resolve "^0.6.0" + +rollup-plugin-terser@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" + integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ== + dependencies: + "@babel/code-frame" "^7.10.4" + jest-worker "^26.2.1" + serialize-javascript "^4.0.0" + terser "^5.0.0" + +rollup@^2.68.0: + version "2.79.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" + integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== + optionalDependencies: + fsevents "~2.3.2" + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" + +source-map-resolve@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" + integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + +source-map-support@0.5.21, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +terser@^5.0.0: + version "5.16.8" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.8.tgz#ccde583dabe71df3f4ed02b65eb6532e0fae15d5" + integrity sha512-QI5g1E/ef7d+PsDifb+a6nnVgC4F22Bg6T0xrBrz6iloVB4PUkkunp6V8nzoOOZJIzjWVdAGqCdlKlhLq/TbIA== + dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" + commander "^2.20.0" + source-map-support "~0.5.20" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tslib@^2.3.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + +typescript@3.9.5: + version "3.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" + integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== + +typescript@4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + +typescript@^3.7.2: + version "3.9.10" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" + integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== + +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 8c41d72efc62f6916100625bf7e73a2e22e222d1 Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Fri, 21 Apr 2023 04:10:16 +0200 Subject: [PATCH 5/5] Diktat fixes --- README.md | 5 +++-- .../akuleshov7/buildutils/DiktatConfiguration.kt | 2 +- gradle.properties | 4 +++- .../ktoml/decoders/DateTimeDecoderTest.kt | 2 +- .../ktoml/decoders/DottedKeysDecoderTest.kt | 2 +- .../ktoml/decoders/GeneralDecoderTest.kt | 10 +++++----- .../ktoml/decoders/NullableTablesTest.kt | 4 ++-- .../ktoml/decoders/StringsDecoderTest.kt | 2 +- .../kotlin/com/akuleshov7/ktoml/file/FileUtils.kt | 2 +- .../com/akuleshov7/ktoml/file/TomlFileReader.kt | 1 - .../com/akuleshov7/ktoml/file/TomlFileWriter.kt | 4 +--- .../akuleshov7/ktoml/file/TomlFileParserTest.kt | 4 ++-- .../akuleshov7/ktoml/source/TomlSourceReader.kt | 12 ++++++++---- .../com/akuleshov7/ktoml/source/JvmStreams.kt | 8 ++++---- .../com/akuleshov7/ktoml/source/StreamTests.kt | 15 ++++++++------- 15 files changed, 41 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 5c7b45c8..c132f495 100644 --- a/README.md +++ b/README.md @@ -117,8 +117,9 @@ implementation("com.akuleshov7:ktoml-file:0.4.0") ## How to use :heavy_exclamation_mark: as TOML is a foremost language for config files, we have also supported the deserialization from file. However, we are using [okio](https://github.com/square/okio) to read the file, so it will be added as a dependency to your -project if you will import [ktoml-file](https://search.maven.org/artifact/com.akuleshov7/ktoml-file). -For basic scenarios of decoding strings you can simple use [ktoml-core](https://search.maven.org/artifact/com.akuleshov7/ktoml-core). +project if you will import [ktoml-file](https://search.maven.org/artifact/com.akuleshov7/ktoml-file). +Same about okio `Source` (for example if you need Streaming): [ktoml-source](https://search.maven.org/artifact/com.akuleshov7/ktoml-source). +For basic scenarios of decoding strings you can simply use [ktoml-core](https://search.maven.org/artifact/com.akuleshov7/ktoml-core). :heavy_exclamation_mark: don't forget to add the serialization plugin `kotlin("plugin.serialization")` to your project. Otherwise, `@Serialization` annotation won't work properly. diff --git a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt b/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt index 02e9c1b1..43b2e701 100644 --- a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt +++ b/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt @@ -33,7 +33,7 @@ fun Project.configureDiktat() { exclude("src/test/**/*.kt", "src/commonTest/**/*.kt") // path matching this pattern will not be checked by diktat } else { include("src/**/*.kt", "**/*.kts") - exclude("src/**test/**/*.kt", "src/commonTest/**/*.kt") // path matching this pattern will not be checked by diktat + exclude("src/**test/**/*.kt", "src/commonTest/**/*.kt", "src/jvmTest/**/*.kt") // path matching this pattern will not be checked by diktat } } } diff --git a/gradle.properties b/gradle.properties index f61da98c..28d26524 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,4 +5,6 @@ kotlin.code.style=official kotlin.mpp.stability.nowarn=true # gradle performance org.gradle.parallel=true -org.gradle.vfs.watch=true \ No newline at end of file +org.gradle.vfs.watch=true + +org.gradle.jvmargs=-Xmx4096M \ No newline at end of file diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DateTimeDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DateTimeDecoderTest.kt index 2d351b67..6c1365cd 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DateTimeDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DateTimeDecoderTest.kt @@ -53,7 +53,7 @@ class DateTimeDecoderTest { expectedLocalTime, "1979-05-27T00:32:00-07:00" ), - Toml().decodeFromString(toml) + Toml.decodeFromString(toml) ) } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DottedKeysDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DottedKeysDecoderTest.kt index 1f8e1b2f..58a4582f 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DottedKeysDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DottedKeysDecoderTest.kt @@ -108,7 +108,7 @@ class DottedKeysDecoderTest { table2 = Table2(b = B(f = 2, d = 2), table2 = InnerTable2in2(myFieldWithSerialName = C(d = 2))), table3 = Table3(notRequiredFieldBecauseOfEmptyTable = 0) ), - Toml().decodeFromString( + Toml.decodeFromString( """ table2.b.d = 2 [table1] diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt index beb39b82..6d3c5096 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt @@ -311,7 +311,7 @@ class GeneralDecoderTest { Regression( General("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") ), - Toml().decodeFromString(test) + Toml.decodeFromString(test) ) } @@ -323,7 +323,7 @@ class GeneralDecoderTest { """.trimIndent() assertEquals( Regression(General("dgfdgfd # f # hi")), - Toml().decodeFromString(test) + Toml.decodeFromString(test) ) test = """ @@ -334,7 +334,7 @@ class GeneralDecoderTest { assertEquals( Regression(General(null)), - Toml().decodeFromString(test) + Toml.decodeFromString(test) ) test = """ @@ -344,7 +344,7 @@ class GeneralDecoderTest { assertEquals( Regression(General(" hello ")), - Toml().decodeFromString(test) + Toml.decodeFromString(test) ) test = """ @@ -354,7 +354,7 @@ class GeneralDecoderTest { assertEquals( Regression(GeneralInt(0)), - Toml().decodeFromString(test) + Toml.decodeFromString(test) ) } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/NullableTablesTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/NullableTablesTest.kt index d3c9db99..7b820f26 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/NullableTablesTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/NullableTablesTest.kt @@ -81,7 +81,7 @@ class NullableTablesTest { class EmptyTomlTest { @Test fun emptyToml() { - var res = Toml().decodeFromString( + var res = Toml.decodeFromString( """ @@ -90,7 +90,7 @@ class EmptyTomlTest { assertEquals(Config(), res) - res = Toml().decodeFromString( + res = Toml.decodeFromString( "".trimIndent() ) diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt index 8c212e75..96fac00e 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt @@ -139,7 +139,7 @@ class StringDecoderTest { regex = '' """ - val decoded = Toml().decodeFromString(test) + val decoded = Toml.decodeFromString(test) assertEquals( Literals( null, 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 50c6428f..f9ace4c3 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 @@ -29,7 +29,7 @@ internal fun getFileSource(filePath: String): Source { /** * Opens a file for writing via a [BufferedSink]. * - * @param tomlFile The path string pointing to a .toml file. + * @param filePath The path string pointing to a .toml file. * @return A [BufferedSink] writing to the specified [tomlFile] path. * @throws FileNotFoundException */ diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt index 93d1eb4f..7548d36e 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileReader.kt @@ -1,7 +1,6 @@ package com.akuleshov7.ktoml.file -import com.akuleshov7.ktoml.TomlConfig import com.akuleshov7.ktoml.TomlInputConfig import com.akuleshov7.ktoml.TomlOutputConfig import com.akuleshov7.ktoml.source.TomlSourceReader 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 e09c06c3..e2c4195f 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 @@ -1,14 +1,12 @@ package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.TomlConfig import com.akuleshov7.ktoml.TomlInputConfig import com.akuleshov7.ktoml.TomlOutputConfig import com.akuleshov7.ktoml.encoders.TomlMainEncoder import okio.use -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule @@ -17,7 +15,7 @@ import kotlinx.serialization.modules.SerializersModule * Writes to a file in the TOML format. * @property serializersModule */ -@OptIn(ExperimentalSerializationApi::class) +@Suppress("SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY") public open class TomlFileWriter : Toml { public constructor( inputConfig: TomlInputConfig = TomlInputConfig(), diff --git a/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt index 9a9fa2aa..4c2c8857 100644 --- a/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt +++ b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt @@ -87,8 +87,8 @@ class TomlFileParserTest { assertEquals(test, TomlFileReader.decodeFromFile(serializer(), file)) // ==== checking how table discovery works - val parsedResult = getFileSource(file).useLines { lines -> - TomlParser(TomlInputConfig()).parseStringsToTomlTree(lines, TomlInputConfig()) + val parsedResult = getFileSource(file).useLines { + TomlParser(TomlInputConfig()).parseStringsToTomlTree(it, TomlInputConfig()) } assertEquals( diff --git a/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt index 6bb7064d..2983ebbe 100644 --- a/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt +++ b/ktoml-source/src/commonMain/kotlin/com/akuleshov7/ktoml/source/TomlSourceReader.kt @@ -22,7 +22,11 @@ public open class TomlSourceReader( inputConfig: TomlInputConfig = TomlInputConfig(), outputConfig: TomlOutputConfig = TomlOutputConfig(), override val serializersModule: SerializersModule = EmptySerializersModule -) : Toml(inputConfig, outputConfig, serializersModule) { +) : Toml( + inputConfig, + outputConfig, + serializersModule +) { /** * Simple deserializer of a source that contains toml. * @@ -33,7 +37,7 @@ public open class TomlSourceReader( public fun decodeFromSource( deserializer: DeserializationStrategy, source: Source, - ): T = source.useLines { lines -> decodeFromString(deserializer, lines, inputConfig) } + ): T = source.useLines { decodeFromString(deserializer, it, inputConfig) } /** * Simple deserializer of a source that contains toml. @@ -60,8 +64,8 @@ public open class TomlSourceReader( deserializer: DeserializationStrategy, source: Source, tomlTableName: String, - ): T = source.useLines { lines -> - partiallyDecodeFromLines(deserializer, lines, tomlTableName, inputConfig) + ): T = source.useLines { + partiallyDecodeFromLines(deserializer, it, tomlTableName, inputConfig) } /** diff --git a/ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt b/ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt index 53900d92..61addd19 100644 --- a/ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt +++ b/ktoml-source/src/jvmMain/kotlin/com/akuleshov7/ktoml/source/JvmStreams.kt @@ -23,8 +23,8 @@ import kotlinx.serialization.serializer public fun Toml.decodeFromStream( deserializer: DeserializationStrategy, stream: InputStream -): T = stream.source().useLines { lines -> - decodeFromString(deserializer, lines) +): T = stream.source().useLines { + decodeFromString(deserializer, it) } /** @@ -52,8 +52,8 @@ public fun Toml.partiallyDecodeFromStream( deserializer: DeserializationStrategy, stream: InputStream, tomlTableName: String -): T = stream.source().useLines { lines -> - partiallyDecodeFromLines(deserializer, lines, tomlTableName) +): T = stream.source().useLines { + partiallyDecodeFromLines(deserializer, it, tomlTableName) } /** diff --git a/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt b/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt index 51841855..80d9a3f8 100644 --- a/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt +++ b/ktoml-source/src/jvmTest/kotlin/com/akuleshov7/ktoml/source/StreamTests.kt @@ -30,7 +30,7 @@ class StreamTests { ) assertEquals( expected, - Toml().decodeFromStream(getTestDataStream("simple_example.toml")) + Toml.decodeFromStream(getTestDataStream("simple_example.toml")) ) } @@ -42,7 +42,7 @@ class StreamTests { A(Ab(InnerTest("Undefined")), InnerTest("Undefined")), D(InnerTest("Undefined")) ) - assertEquals(test, Toml().decodeFromStream(getTestDataStream("complex_toml_tables.toml"))) + assertEquals(test, Toml.decodeFromStream(getTestDataStream("complex_toml_tables.toml"))) // ==== checking how table discovery works val parsedResult = getTestDataStream("complex_toml_tables.toml").source().useLines { lines -> @@ -57,7 +57,7 @@ class StreamTests { @Test fun regressionCast2Test() { val parsedResult = - Toml().decodeFromStream(getTestDataStream("class_cast_regression2.toml")) + Toml.decodeFromStream(getTestDataStream("class_cast_regression2.toml")) assertEquals(RegressionTest(null, 1, 2, null), parsedResult) } @@ -65,7 +65,7 @@ class StreamTests { @Test fun regressionPartialTest() { val parsedResult = - Toml().decodeFromStream(getTestDataStream("class_cast_regression2.toml")) + Toml.decodeFromStream(getTestDataStream("class_cast_regression2.toml")) assertEquals(RegressionTest(null, 1, 2, null), parsedResult) } @@ -82,7 +82,7 @@ class StreamTests { includedTests = null, ignoreSaveComments = null ), - Toml().partiallyDecodeFromStream( + Toml.partiallyDecodeFromStream( getTestDataStream("partial_parser_regression.toml"), "general" ) @@ -103,7 +103,7 @@ class StreamTests { warn = WarnConfig(list = listOf("12a", "12f")), list3 = listOf("mystr", "2", "3") ), - Toml().decodeFromStream(getTestDataStream("partial_parser_regression.toml")) + Toml.decodeFromStream(getTestDataStream("partial_parser_regression.toml")) ) } @@ -112,7 +112,7 @@ class StreamTests { val test = TwoTomlTables(Table1(1, 2), Table2(1, 2, 3)) assertEquals( test.table1, - Toml().partiallyDecodeFromStream( + Toml.partiallyDecodeFromStream( getTestDataStream("partial_decoder.toml"), "table1" ) @@ -135,6 +135,7 @@ class StreamTests { } private fun getTestDataStream(name: String): InputStream = requireNotNull(StreamTests::class.java.getResourceAsStream(name)) + /** * @property title * @property owner