diff --git a/README.md b/README.md index c5ea24ad..2ac72454 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ As this young and big project [is needed](https://github.com/Kotlin/kotlinx.seri 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! -**Thanks!** :pray: +**Thanks!** :pray: :partying_face: ## Acknowledgement Special thanks to those awesome developers who give us great suggestions, help us to maintain and improve this project: diff --git a/build.gradle.kts b/build.gradle.kts index 88391e22..c997f44d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,10 @@ allprojects { } configureDiktat() configureDetekt() + + tasks.withType { + jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") + } } createDiktatTask() createDetektTask() diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index c32f12a9..5ac877d5 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,9 +8,9 @@ repositories { dependencies { // this hack prevents the following bug: https://github.com/gradle/gradle/issues/9770 - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") - implementation("org.cqfn.diktat:diktat-gradle-plugin:1.0.0-rc.3") + implementation("org.cqfn.diktat:diktat-gradle-plugin:1.0.2") implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.15.0") implementation("io.github.gradle-nexus:publish-plugin:1.1.0") implementation("org.ajoberstar.reckon:reckon-gradle:0.13.0") diff --git a/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt b/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt index da58fda7..317c380c 100644 --- a/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt +++ b/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt @@ -7,8 +7,8 @@ // now it is just a workaround object Versions { - const val KOTLIN = "1.6.0" + const val KOTLIN = "1.6.10" const val JUNIT = "5.7.1" const val OKIO = "3.0.0" - const val SERIALIZATION = "1.3.1" + const val SERIALIZATION = "1.3.2" } diff --git a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt b/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt index 71492afc..8f080457 100644 --- a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt +++ b/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/DiktatConfiguration.kt @@ -17,10 +17,10 @@ fun Project.configureDiktat() { apply() configure { diktatConfigFile = rootProject.file("diktat-analysis.yml") - inputs = files( - "src/**/*.kt" - ) - excludes = files("src/commonTest") + inputs { + include("src/**/*.kt", "*.kts", "src/**/*.kts") + exclude("$projectDir/build/**", "src/commonTest/**/*.kt") + } } } @@ -33,10 +33,15 @@ fun Project.createDiktatTask() { apply() configure { diktatConfigFile = rootProject.file("diktat-analysis.yml") - inputs = files( - "$rootDir/buildSrc/src/**/*.kt" - ) - excludes = files("src/commonTest") + inputs { + include( + "$rootDir/buildSrc/src/**/*.kt", + "$rootDir/buildSrc/src/**/*.kts", + "$rootDir/*.kts", + "$rootDir/buildSrc/*.kts" + ) + exclude("$rootDir/build", "$rootDir/buildSrc/build") + } } } tasks.register("diktatCheckAll") { diff --git a/diktat-analysis.yml b/diktat-analysis.yml index db1b6914..90412414 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -32,7 +32,7 @@ configuration: useRecommendedImportsOrder: true - name: FILE_WILDCARD_IMPORTS - enabled: true + enabled: false configuration: allowedWildcards: "kotlinx.serialization.*" - name: BRACES_BLOCK_STRUCTURE_ERROR diff --git a/ktoml-core/build.gradle.kts b/ktoml-core/build.gradle.kts index 59d344e7..38c005f2 100644 --- a/ktoml-core/build.gradle.kts +++ b/ktoml-core/build.gradle.kts @@ -1,6 +1,4 @@ import com.akuleshov7.buildutils.configurePublishing -import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentOperatingSystem -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest plugins { 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 b82f10b8..a15e2e12 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt @@ -3,6 +3,7 @@ package com.akuleshov7.ktoml import com.akuleshov7.ktoml.decoders.TomlMainDecoder import com.akuleshov7.ktoml.exceptions.MissingRequiredPropertyException import com.akuleshov7.ktoml.parsers.TomlParser +import com.akuleshov7.ktoml.tree.TableType import com.akuleshov7.ktoml.tree.TomlFile import com.akuleshov7.ktoml.writers.TomlWriter @@ -89,7 +90,7 @@ public open class Toml( tomlTableName: String, config: TomlConfig = TomlConfig() ): T { - val fakeFileNode = generateFakeTomlStructureForPartialParsing(toml, tomlTableName, config, TomlParser::parseString) + val fakeFileNode = generateFakeTomlStructureForPartialParsing(toml, tomlTableName, config, TableType.PRIMITIVE, TomlParser::parseString) return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config) } @@ -117,6 +118,7 @@ public open class Toml( toml.joinToString("\n"), tomlTableName, config, + TableType.PRIMITIVE, TomlParser::parseString, ) return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config) @@ -128,10 +130,11 @@ public open class Toml( toml: String, tomlTableName: String, config: TomlConfig = TomlConfig(), + type: TableType, parsingFunction: (TomlParser, String) -> TomlFile ): TomlFile { val parsedToml = parsingFunction(TomlParser(this.config), toml) - .findTableInAstByName(tomlTableName, tomlTableName.count { it == '.' } + 1) + .findTableInAstByName(tomlTableName, tomlTableName.count { it == '.' } + 1, type) ?: throw MissingRequiredPropertyException( "Cannot find table with name <$tomlTableName> in the toml input. " + "Not able to decode this toml part." 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 a3a5766b..c658b87f 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 @@ -76,7 +76,7 @@ public class TomlMainDecoder( is TomlKeyValueArray -> node // empty nodes will be filtered by iterateUntilWillFindAnyKnownName() method, but in case we came into this // branch, we should throw an exception as it is not expected at all and we should catch this in tests - is TomlStubEmptyNode, is TomlTable, is TomlFile -> + else -> throw InternalDecodingException( "This kind of node should not be processed in TomlDecoder.decodeValue(): ${node.content}" ) @@ -201,7 +201,7 @@ public class TomlMainDecoder( when (nextProcessingNode) { is TomlKeyValueArray -> TomlArrayDecoder(nextProcessingNode, config) is TomlKeyValuePrimitive, is TomlStubEmptyNode -> TomlMainDecoder(nextProcessingNode, config) - is TomlTable -> { + 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}>" 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 0bd2ba4b..36942b33 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 @@ -7,7 +7,7 @@ package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.exceptions.ParseException /** - * Splitting dot-separated string to tokens: + * Splitting dot-separated string to the list of tokens: * a.b.c -> [a, b, c]; a."b.c".d -> [a, "b.c", d]; * * @param lineNo - the line number in toml @@ -74,6 +74,14 @@ internal fun String.trimQuotes(): String = trimSymbols(this, "\"", "\"") */ internal fun String.trimBrackets(): String = trimSymbols(this, "[", "]") +/** + * If this string starts and end with a pair brackets([[]]) - will return the string with brackets removed + * Otherwise, returns this string. + * + * @return string with the result + */ +internal fun String.trimDoubleBrackets(): String = trimSymbols(this, "[[", "]]") + private fun String.validateSpaces(lineNo: Int, fullKey: String) { if (this.trim().count { it == ' ' } > 0 && this.isNotQuoted()) { throw ParseException( 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 f479401a..81c81a8c 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/TomlParser.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/TomlParser.kt @@ -2,14 +2,8 @@ package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.TomlConfig import com.akuleshov7.ktoml.exceptions.InternalAstException -import com.akuleshov7.ktoml.tree.TomlFile +import com.akuleshov7.ktoml.tree.* import com.akuleshov7.ktoml.tree.TomlKeyValue -import com.akuleshov7.ktoml.tree.TomlKeyValueArray -import com.akuleshov7.ktoml.tree.TomlKeyValuePrimitive -import com.akuleshov7.ktoml.tree.TomlNode -import com.akuleshov7.ktoml.tree.TomlStubEmptyNode -import com.akuleshov7.ktoml.tree.TomlTable -import com.akuleshov7.ktoml.tree.splitKeyValue import kotlin.jvm.JvmInline /** @@ -38,45 +32,60 @@ 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 { - var currentParent: TomlNode = TomlFile(config) - val tomlFileHead = currentParent as TomlFile + var currentParentalNode: TomlNode = TomlFile(config) + val tomlFileHead = currentParentalNode as TomlFile // need to trim empty lines BEFORE the start of processing val mutableTomlLines = tomlLines.toMutableList().trimEmptyLines() mutableTomlLines.forEachIndexed { index, line -> 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()) { - val tableSection = TomlTable(line, lineNo, config) - // if the table is the last line in toml, than it has no children and we need to - // add at least fake node as a child - if (index == mutableTomlLines.lastIndex) { - tableSection.appendChild(TomlStubEmptyNode(lineNo, config)) - } - // covering the case when processed table contains no key-value pairs or no tables (after our insertion) - // adding fake nodes to a previous table (it has no children because we have found another table right after) - if (currentParent.hasNoChildren()) { - currentParent.appendChild(TomlStubEmptyNode(currentParent.lineNo, config)) + if (line.isArrayOfTables()) { + // TomlArrayOfTables contains all information about the ArrayOfTables ([[array of tables]]) + val tableArray = TomlArrayOfTables(line, lineNo, config) + val arrayOfTables = tomlFileHead.insertTableToTree(tableArray, TableType.ARRAY) + // 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) + // adding this element as a child to the array of tables + arrayOfTables.appendChild(newArrayElement) + // and setting this element as a current parent, so new key-records will be added to this bucket + currentParentalNode = newArrayElement + } else { + 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) { + tableSection.appendChild(TomlStubEmptyNode(lineNo, config)) + } + // 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) + if (currentParentalNode.hasNoChildren()) { + currentParentalNode.appendChild(TomlStubEmptyNode(currentParentalNode.lineNo, config)) + } + currentParentalNode = tomlFileHead.insertTableToTree(tableSection, TableType.PRIMITIVE) } - currentParent = tomlFileHead.insertTableToTree(tableSection) } else { val keyValue = line.parseTomlKeyValue(lineNo, config) if (keyValue !is TomlNode) { throw InternalAstException("All Toml nodes should always inherit TomlNode class." + " Check [${keyValue.key}] with $keyValue type") } - + // inserting the key-value record to the tree if (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(currentParent, config) + val newTableSection = keyValue.createTomlTableFromDottedKey(currentParentalNode, config) tomlFileHead - .insertTableToTree(newTableSection) + .insertTableToTree(newTableSection, TableType.PRIMITIVE) .appendChild(keyValue) } else { // otherwise it should simply append the keyValue to the parent - currentParent.appendChild(keyValue) + currentParentalNode.appendChild(keyValue) } } } @@ -109,6 +118,11 @@ public value class TomlParser(private val config: TomlConfig) { } } + private fun String.isArrayOfTables(): Boolean { + val trimmed = this.trim() + return trimmed.startsWith("[[") && trimmed.endsWith("]]") + } + private fun String.isTableNode(): Boolean { val trimmed = this.trim() return trimmed.startsWith("[") && trimmed.endsWith("]") 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 new file mode 100644 index 00000000..16dbfa0a --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlArrayOfTables.kt @@ -0,0 +1,65 @@ +/** + * 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.splitKeyToTokens +import com.akuleshov7.ktoml.parsers.trimDoubleBrackets +import com.akuleshov7.ktoml.parsers.trimQuotes + +/** + * @property isSynthetic + */ +// FixMe: this class is mostly identical to the TomlTable - we should unify them together +public class TomlArrayOfTables( + content: String, + lineNo: Int, + config: TomlConfig = TomlConfig(), + public val isSynthetic: Boolean = false +) : TomlTable( + content, + lineNo, + config +) { + 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 { + // getting the content inside brackets ([a.b] -> a.b) + val sectionFromContent = content.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] } + } + } +} + +/** + * 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 +} 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 new file mode 100644 index 00000000..ae29d750 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlFile.kt @@ -0,0 +1,18 @@ +package com.akuleshov7.ktoml.tree + +import com.akuleshov7.ktoml.TomlConfig +import com.akuleshov7.ktoml.exceptions.InternalAstException + +/** + * 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(): MutableSet = + throw InternalAstException("Invalid call to getNeighbourNodes() for TomlFile 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/TomlKeyValue.kt index 06be6bdc..9cbfb726 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValue.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValue.kt @@ -22,7 +22,7 @@ internal interface TomlKeyValue { * @param config * @return the table that is parsed from a dotted key */ - fun createTomlTableFromDottedKey(parentNode: TomlNode, config: TomlConfig = TomlConfig()): TomlTable { + fun createTomlTableFromDottedKey(parentNode: TomlNode, config: TomlConfig = TomlConfig()): 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 @@ -30,9 +30,9 @@ internal interface TomlKeyValue { // 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 TomlTablePrimitive) "${parentNode.fullTableName}." else "" // and creating a new table that will be created from dotted key - return TomlTable( + return TomlTablePrimitive( "[$parentalPrefix${syntheticTablePrefix.joinToString(".")}]", lineNo, config, 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 new file mode 100644 index 00000000..a6cbfbb1 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValueArray.kt @@ -0,0 +1,35 @@ +package com.akuleshov7.ktoml.tree + +import com.akuleshov7.ktoml.TomlConfig + +/** + * 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 + ) +} 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 new file mode 100644 index 00000000..ea7022d6 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValuePrimitive.kt @@ -0,0 +1,35 @@ +package com.akuleshov7.ktoml.tree + +import com.akuleshov7.ktoml.TomlConfig + +/** + * 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 + ) +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlNode.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlNode.kt index 30dc80fc..df390980 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlNode.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlNode.kt @@ -5,11 +5,9 @@ package com.akuleshov7.ktoml.tree import com.akuleshov7.ktoml.TomlConfig -import com.akuleshov7.ktoml.exceptions.InternalAstException import com.akuleshov7.ktoml.exceptions.ParseException -import com.akuleshov7.ktoml.parsers.splitKeyToTokens -import com.akuleshov7.ktoml.parsers.trimBrackets -import com.akuleshov7.ktoml.parsers.trimQuotes + +public const val EMPTY_TECHNICAL_NODE: String = "technical_node" /** * Base Node class for AST. @@ -35,11 +33,12 @@ public sealed class TomlNode( // this constructor is used by TomlKeyValueList and TomlKeyValuePrimitive and we concatenate keyValuePair to the content // only for logging, debug information and unification of the code - private constructor( + protected constructor( key: TomlKey, value: TomlValue, lineNo: Int, - config: TomlConfig = TomlConfig()) : this( + config: TomlConfig = TomlConfig() + ) : this( "${key.content}=${value.content}", lineNo, config @@ -72,17 +71,20 @@ public sealed class TomlNode( private fun findTableInAstByName( searchedTableName: String, searchedLevel: Int, - currentLevel: Int + currentLevel: Int, + type: TableType ): List { val result = - if (this is TomlTable && this.fullTableName == searchedTableName && currentLevel == searchedLevel) { + // we need to filter nodes by the type of table that we are inserting to the tree (array/primitive) + if (this is TomlTable && this.type == type && + this.fullTableName == searchedTableName && currentLevel == searchedLevel) { mutableListOf(this) } else { mutableListOf() } return result + this.children.flatMap { if (currentLevel + 1 <= searchedLevel) { - it.findTableInAstByName(searchedTableName, searchedLevel, currentLevel + 1) + it.findTableInAstByName(searchedTableName, searchedLevel, currentLevel + 1, type) } else { mutableListOf() } @@ -102,11 +104,16 @@ public sealed class TomlNode( * a.c a.d * \ * a.d.e + * @param type * @return table that was found or null in case of not found * @throws ParseException if found several tables with the same name */ - public fun findTableInAstByName(searchedTableName: String, searchedLevel: Int): TomlTable? { - val searchedTable = findTableInAstByName(searchedTableName, searchedLevel, 0) + public fun findTableInAstByName( + searchedTableName: String, + searchedLevel: Int, + type: TableType + ): TomlTable? { + val searchedTable = findTableInAstByName(searchedTableName, searchedLevel, 0, type) if (searchedTable.size > 1) { throw ParseException( @@ -122,9 +129,10 @@ public sealed class TomlNode( * (even parental). For [a.b.c] it will create 3 nodes: a, b, and c * * @param tomlTable - a table (section) that should be inserted into the tree + * @param type * @return inserted table */ - public fun insertTableToTree(tomlTable: TomlTable): TomlNode { + public fun insertTableToTree(tomlTable: T, type: TableType): TomlNode { // prevParentNode - saved node that is used in a chain var prevParentNode: TomlNode = this // [a.b.c.d] -> for each section node checking existing node in a tree @@ -132,12 +140,12 @@ public sealed class TomlNode( // // the only trick here is to save the link to the initial tomlTable (append it in the end) tomlTable.tablesList.forEachIndexed { level, tableName -> - val foundTableName = this.findTableInAstByName(tableName, level + 1) + val foundTableName = this.findTableInAstByName(tableName, level + 1, type) foundTableName?.let { prevParentNode = it } ?: run { - // if we came to the last part of table - just insert our table to the end + // if we came to the last part (to 'd' from a.b.c.d) of the table - just will insert our table to the end prevParentNode = if (level == tomlTable.tablesList.size - 1) { prevParentNode.appendChild(tomlTable) tomlTable @@ -145,7 +153,7 @@ public sealed class TomlNode( // hack and trick to save the link to the initial node (that was passed as an argument) in the tree // so the node will be added only in the end, and it will be the initial node // (!) we will mark these tables with 'isSynthetic' flag - val newChildTableName = TomlTable("[$tableName]", lineNo, config, true) + val newChildTableName = TomlTablePrimitive("[$tableName]", lineNo, config, true) prevParentNode.appendChild(newChildTableName) newChildTableName } @@ -172,8 +180,8 @@ public sealed class TomlNode( * * @return all detected toml tables */ - public fun getAllChildTomlTables(): List { - val result = if (this is TomlTable) mutableListOf(this) else mutableListOf() + public fun getAllChildTomlTables(): List { + val result = if (this is TomlTablePrimitive) mutableListOf(this) else mutableListOf() return result + this.children.flatMap { it.getAllChildTomlTables() } @@ -184,7 +192,7 @@ public sealed class TomlNode( * * @return all real table nodes */ - public fun getRealTomlTables(): List = + public fun getRealTomlTables(): List = this.getAllChildTomlTables().filter { !it.isSynthetic } public companion object { @@ -206,143 +214,3 @@ public sealed class TomlNode( } } } - -/** - * 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(): MutableSet = - throw InternalAstException("Invalid call to getNeighbourNodes() for TomlFile node") -} - -/** - * tablesList - a list of names of sections (tables) that are included into this particular TomlTable - * @property isSynthetic - flag to determine that this node was synthetically and there are no such table in the input - * for example: if the TomlTable is [a.b.c] this list will contain [a], [a.b], [a.b.c] - */ -// FixMe: as diktat fixer can in some cases break the code (https://github.com/cqfn/diKTat/issues/966), -// we will suppress this rule -@Suppress("MULTIPLE_INIT_BLOCKS") -public class TomlTable( - content: String, - lineNo: Int, - config: TomlConfig = TomlConfig(), - public val isSynthetic: Boolean = false) : TomlNode( - content, - lineNo, - config) { - // list of tables that are included in this table (e.g.: {a, a.b, a.b.c} in a.b.c) - public var tablesList: List - - // short table name (only the name without parental prefix, like a - it is used in decoder and encoder) - override val name: String - - // this name is used during the injection of the table to the AST - public val nameWithQuotes: String - - // full name of the table (like a.b.c.d) - public var fullTableName: String - - // number of nodes in current table (starting from 0) - internal var level: Int - - init { - // getting the content inside brackets ([a.b] -> a.b) - val sectionFromContent = content.trim().trimBrackets().trim() - - if (sectionFromContent.isBlank()) { - throw ParseException("Incorrect blank table name: $content", lineNo) - } - - fullTableName = sectionFromContent - level = sectionFromContent.count { it == '.' } - - val sectionsList = sectionFromContent.splitKeyToTokens(lineNo) - name = sectionsList.last().trimQuotes() - nameWithQuotes = sectionsList.last() - tablesList = sectionsList.mapIndexed { index, _ -> - (0..index).joinToString(".") { sectionsList[it] } - } - } -} - -/** - * Class for parsing and storing Array in AST. It receives a pair of two strings as an input and converts it to a pair - * of TomlKey and TomlValue (as TomlArray) - * @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 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 - ) -} - -/** - * 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 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 - ) -} - -/** - * this is a hack to cover empty TOML tables that have missing key-values - * According the spec: "Empty tables are allowed and simply have no key/value pairs within them." - * - * 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", - lineNo, - config) { - override val name: String = "empty_technical_node" -} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlStubEmptyNode.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlStubEmptyNode.kt new file mode 100644 index 00000000..9bee8f66 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlStubEmptyNode.kt @@ -0,0 +1,17 @@ +package com.akuleshov7.ktoml.tree + +import com.akuleshov7.ktoml.TomlConfig + +/** + * this is a hack to cover empty TOML tables that have missing key-values + * According the spec: "Empty tables are allowed and simply have no key/value pairs within them." + * + * 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, + lineNo, + config +) { + override val name: String = EMPTY_TECHNICAL_NODE +} 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 new file mode 100644 index 00000000..6095fe47 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlTable.kt @@ -0,0 +1,36 @@ +/** + * Common class for tables + */ + +package com.akuleshov7.ktoml.tree + +import com.akuleshov7.ktoml.TomlConfig + +/** + * 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 + */ +public abstract class TomlTable( + override val content: String, + override val lineNo: Int, + override val config: TomlConfig = TomlConfig() +) : TomlNode( + content, + lineNo, + config +) { + public abstract var fullTableName: String + public abstract var tablesList: List + public abstract val type: TableType +} + +/** + * 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 new file mode 100644 index 00000000..d5bc8fee --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlTablePrimitive.kt @@ -0,0 +1,56 @@ +/** + * 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.splitKeyToTokens +import com.akuleshov7.ktoml.parsers.trimBrackets +import com.akuleshov7.ktoml.parsers.trimQuotes + +/** + * 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(), + public val isSynthetic: Boolean = false +) : TomlTable( + content, + lineNo, + config +) { + 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 { + // getting the content inside brackets ([a.b] -> a.b) + val sectionFromContent = content.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] } + } + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt index 0af5521c..ecfcbd36 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt @@ -204,7 +204,8 @@ internal constructor( ) : this( rawContent.parse(lineNo, config), rawContent, - lineNo) { + lineNo + ) { validateBrackets() } 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 066f006c..d0beff21 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 @@ -146,7 +146,8 @@ public abstract class TomlEmitter(config: TomlConfig) { public fun emitValue( string: String, isLiteral: Boolean = false, - isMultiline: Boolean = false): Unit = + isMultiline: Boolean = false + ): Unit = if (isMultiline) { val quotes = if (isLiteral) "'''" else "\"\"\"" diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt index 141045cf..dbc04f10 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt @@ -2,13 +2,7 @@ package com.akuleshov7.ktoml.writers import com.akuleshov7.ktoml.TomlConfig import com.akuleshov7.ktoml.exceptions.TomlWritingException -import com.akuleshov7.ktoml.tree.TomlFile -import com.akuleshov7.ktoml.tree.TomlKey -import com.akuleshov7.ktoml.tree.TomlKeyValueArray -import com.akuleshov7.ktoml.tree.TomlKeyValuePrimitive -import com.akuleshov7.ktoml.tree.TomlNode -import com.akuleshov7.ktoml.tree.TomlStubEmptyNode -import com.akuleshov7.ktoml.tree.TomlTable +import com.akuleshov7.ktoml.tree.* import kotlin.jvm.JvmInline /** @@ -37,9 +31,12 @@ public value class TomlWriter(private val config: TomlConfig) { "A file node is not allowed as a child of another file node." ) is TomlKeyValueArray -> TODO() + is TomlArrayOfTablesElement -> TODO() + is TomlArrayOfTables -> TODO() is TomlKeyValuePrimitive -> TODO() is TomlStubEmptyNode -> TODO() is TomlTable -> TODO() + is TomlTablePrimitive -> TODO() } private fun TomlEmitter.writeKey(key: TomlKey) { diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/TomlNodeTest.kt b/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/TomlNodeTest.kt deleted file mode 100644 index c19d8349..00000000 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/TomlNodeTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.akulashov7.ktoml.parsers - -import com.akuleshov7.ktoml.tree.TomlFile -import com.akuleshov7.ktoml.tree.TomlTable -import kotlin.test.Test -import kotlin.test.assertTrue - -class TomlNodeTest { - - @Test - fun findTableChildByName() { - val fileNode = prepareTree() - - assertTrue { fileNode.findTableInAstByName("a.d.e", 3)?.content == "[a.d.e]" } - assertTrue { fileNode.findTableInAstByName("a.d", 2)?.content == "[a.d]" } - assertTrue { fileNode.findTableInAstByName("b", 1)?.content == "[b]" } - assertTrue { fileNode.findTableInAstByName("e", 1) == null } - } - - /** - * a b - * / \ \ - * a.c a.d b.a - * \ - * a.d.e - */ - fun prepareTree(): TomlFile { - val fileNode = TomlFile() - val sectionA = TomlTable("[a]", 0) - val sectionAC = TomlTable("[a.c]", 1) - val sectionAD = TomlTable("[a.d]", 2) - val sectionADE = TomlTable("[a.d.e]", 3) - - val sectionB = TomlTable("[b]", 4) - val sectionBA = TomlTable("[b.a]", 5) - - fileNode.appendChild(sectionA) - fileNode.appendChild(sectionB) - - sectionA.appendChild(sectionAC) - sectionA.appendChild(sectionAD) - sectionB.appendChild(sectionBA) - - sectionAD.appendChild(sectionADE) - - return fileNode - } -} \ No newline at end of file diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/ArrayDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ArrayDecoderTest.kt similarity index 99% rename from ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/ArrayDecoderTest.kt rename to ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ArrayDecoderTest.kt index 00da0634..2fede2b9 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/ArrayDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ArrayDecoderTest.kt @@ -1,4 +1,4 @@ -package com.akulashov7.ktoml.decoders +package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.exceptions.CastException 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 new file mode 100644 index 00000000..6fa9d7b8 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ArrayOfTablesDecoderTest.kt @@ -0,0 +1,22 @@ +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) + +class ArrayOfTablesDecoderTest { + @Test + fun testRegressions() { + + + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/DecodingTypeTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DecodingTypeTest.kt similarity index 97% rename from ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/DecodingTypeTest.kt rename to ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DecodingTypeTest.kt index 12599c17..5f6b03da 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/DecodingTypeTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DecodingTypeTest.kt @@ -1,4 +1,4 @@ -package com.akulashov7.ktoml.decoders +package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.exceptions.IllegalTypeException diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/DottedKeysDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DottedKeysDecoderTest.kt similarity index 99% rename from ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/DottedKeysDecoderTest.kt rename to ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DottedKeysDecoderTest.kt index 7a462de1..e8dba493 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/DottedKeysDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/DottedKeysDecoderTest.kt @@ -1,4 +1,4 @@ -package com.akulashov7.ktoml.decoders +package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.TomlConfig diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/GeneralDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt similarity index 96% rename from ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/GeneralDecoderTest.kt rename to ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt index 5a415e7e..d20ff102 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/GeneralDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt @@ -1,4 +1,4 @@ -package com.akulashov7.ktoml.decoders +package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.TomlConfig @@ -393,4 +393,25 @@ class GeneralDecoderTest { ), Toml.decodeFromString(test) ) } + + + @Test + // this logic will be changed in https://github.com/akuleshov7/ktoml/issues/30 + fun tablesRedeclaration() { + val test = """ + [table1] + a = 2 + + [table1] + a = 1 + b = 2 + """.trimIndent() + + assertEquals( + SimpleTomlCase( + Table1(1,2) + ) + , Toml.decodeFromString(test) + ) + } } diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/NullableTablesTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/NullableTablesTest.kt similarity index 98% rename from ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/NullableTablesTest.kt rename to ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/NullableTablesTest.kt index e951ace2..273bf58b 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/NullableTablesTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/NullableTablesTest.kt @@ -1,4 +1,4 @@ -package com.akulashov7.ktoml.decoders +package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.TomlConfig diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/PartialDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PartialDecoderTest.kt similarity index 95% rename from ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/PartialDecoderTest.kt rename to ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PartialDecoderTest.kt index b8c1c40b..ec15b326 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/PartialDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PartialDecoderTest.kt @@ -1,4 +1,4 @@ -package com.akulashov7.ktoml.decoders +package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml import kotlinx.serialization.ExperimentalSerializationApi diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/ReadMeExampleTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt similarity index 98% rename from ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/ReadMeExampleTest.kt rename to ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt index 9370b151..7bfde548 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/decoders/ReadMeExampleTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt @@ -1,4 +1,4 @@ -package com.akulashov7.ktoml.decoders +package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml import kotlinx.serialization.decodeFromString 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 new file mode 100644 index 00000000..f01fe4cb --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ArraysOfTablesTest.kt @@ -0,0 +1,84 @@ +package com.akuleshov7.ktoml.parsers + +import com.akuleshov7.ktoml.Toml.Default.tomlParser +import com.akuleshov7.ktoml.exceptions.ParseException +import com.akuleshov7.ktoml.tree.TableType +import com.akuleshov7.ktoml.tree.TomlFile +import com.akuleshov7.ktoml.tree.TomlKey +import com.akuleshov7.ktoml.tree.TomlKeyValuePrimitive +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class ArraysOfTablesTest { + @Test + fun positiveSimpleParsingTest() { + val string = """ + [[fruits]] + a = "apple" + b = "qwerty" + + [[fruits]] + a = "banana" + b = "qwerty" + + [[fruits]] + a = "plantain" + b = "qwerty" + """.trimIndent() + + val parsedToml = tomlParser.parseString(string) + parsedToml.prettyPrint() + val array = parsedToml.findTableInAstByName("fruits", 1, TableType.ARRAY) + assertEquals(3, array?.children?.size) + } + + @Test + fun tomlExampleParsingTest() { + val string = """ + [[fruits]] + name = "apple" + + [fruits.physical] + color = "red" + shape = "round" + + [[fruits.varieties]] + name = "red delicious" + + [[fruits.varieties]] + name = "granny smith" + + [[fruits]] + name = "banana" + + [[fruits.varieties]] + name = "plantain" + """.trimIndent() + + val parsedToml = tomlParser.parseString(string) + parsedToml.prettyPrint() + val array = parsedToml.findTableInAstByName("fruits.varieties", 2, TableType.ARRAY) + assertEquals(3, array?.children?.size) + + } + + @Test + fun parsingRegression() { + val string = """ + [fruits] + name = "apple" + + [fruits] + name = "banana" + + [fruits] + name = "plantain" + """.trimIndent() + + val parsedToml = tomlParser.parseString(string) + parsedToml.prettyPrint() + assertEquals(parsedToml.children.size, 2) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/CommonParserTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/CommonParserTest.kt similarity index 96% rename from ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/CommonParserTest.kt rename to ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/CommonParserTest.kt index 4402a506..88124e43 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/CommonParserTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/CommonParserTest.kt @@ -1,4 +1,4 @@ -package com.akulashov7.ktoml.parsers +package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.tree.TomlArray import com.akuleshov7.ktoml.tree.TomlBasicString diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/DottedKeyParserTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/DottedKeyParserTest.kt similarity index 96% rename from ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/DottedKeyParserTest.kt rename to ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/DottedKeyParserTest.kt index 4d2ce1d7..94fe7a42 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/DottedKeyParserTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/DottedKeyParserTest.kt @@ -1,4 +1,4 @@ -package com.akulashov7.ktoml.parsers +package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.exceptions.ParseException import com.akuleshov7.ktoml.tree.TomlFile @@ -55,6 +55,5 @@ class DottedKeyParserTest { val testKeyValue = TomlKeyValuePrimitive(Pair("a.b.c", "5"), 0) test = testKeyValue.createTomlTableFromDottedKey(TomlFile()) assertEquals("c", testKeyValue.key.content) - assertEquals(1, test.level) } } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TomlNodeTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TomlNodeTest.kt new file mode 100644 index 00000000..cce31360 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TomlNodeTest.kt @@ -0,0 +1,49 @@ +package com.akuleshov7.ktoml.parsers + +import com.akuleshov7.ktoml.tree.TableType +import com.akuleshov7.ktoml.tree.TomlFile +import com.akuleshov7.ktoml.tree.TomlTablePrimitive +import kotlin.test.Test +import kotlin.test.assertTrue + +class TomlNodeTest { + + @Test + fun findTableChildByName() { + val fileNode = prepareTree() + + assertTrue { fileNode.findTableInAstByName("a.d.e", 3, TableType.PRIMITIVE)?.content == "[a.d.e]" } + assertTrue { fileNode.findTableInAstByName("a.d", 2, TableType.PRIMITIVE)?.content == "[a.d]" } + assertTrue { fileNode.findTableInAstByName("b", 1, TableType.PRIMITIVE)?.content == "[b]" } + assertTrue { fileNode.findTableInAstByName("e", 1, TableType.PRIMITIVE) == null } + } + + /** + * a b + * / \ \ + * a.c a.d b.a + * \ + * a.d.e + */ + fun prepareTree(): TomlFile { + val fileNode = TomlFile() + val sectionA = TomlTablePrimitive("[a]", 0) + val sectionAC = TomlTablePrimitive("[a.c]", 1) + val sectionAD = TomlTablePrimitive("[a.d]", 2) + val sectionADE = TomlTablePrimitive("[a.d.e]", 3) + + val sectionB = TomlTablePrimitive("[b]", 4) + val sectionBA = TomlTablePrimitive("[b.a]", 5) + + fileNode.appendChild(sectionA) + fileNode.appendChild(sectionB) + + sectionA.appendChild(sectionAC) + sectionA.appendChild(sectionAD) + sectionB.appendChild(sectionBA) + + sectionAD.appendChild(sectionADE) + + return fileNode + } +} \ No newline at end of file diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/TomlTableTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TomlTableTest.kt similarity index 51% rename from ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/TomlTableTest.kt rename to ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TomlTableTest.kt index 81063166..163a2f2f 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/TomlTableTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/TomlTableTest.kt @@ -1,38 +1,38 @@ -package com.akulashov7.ktoml.parsers +package com.akuleshov7.ktoml.parsers +import com.akuleshov7.ktoml.tree.TableType import com.akuleshov7.ktoml.tree.TomlFile -import com.akuleshov7.ktoml.tree.TomlTable +import com.akuleshov7.ktoml.tree.TomlTablePrimitive import kotlin.test.Test import kotlin.test.assertEquals class TomlTableTest { @Test fun createTomlTable() { - val table = TomlTable("[a.b.c.d.e]", 0) + val table = TomlTablePrimitive("[a.b.c.d.e]", 0) assertEquals(table.tablesList, listOf("a", "a.b", "a.b.c", "a.b.c.d", "a.b.c.d.e")) assertEquals(table.content, "[a.b.c.d.e]") - assertEquals(table.level, 4) assertEquals(table.fullTableName, "a.b.c.d.e") } @Test fun createSimpleTomlTable() { - val table = TomlTable("[a]", 0) - assertEquals(table.level, 0) + val table = TomlTablePrimitive("[a]", 0) + assertEquals(table.fullTableName, "a") } @Test fun insertFirstLevelTableToTreeTest() { val fileNode = prepareTree() - val tableA = TomlTable("[a]", 0) - val tableB = TomlTable("[b]", 0) - val tableC = TomlTable("[c]", 0) - val tableD = TomlTable("[d]", 0) + val tableA = TomlTablePrimitive("[a]", 0) + val tableB = TomlTablePrimitive("[b]", 0) + val tableC = TomlTablePrimitive("[c]", 0) + val tableD = TomlTablePrimitive("[d]", 0) - fileNode.insertTableToTree(tableA) - fileNode.insertTableToTree(tableB) - fileNode.insertTableToTree(tableC) - fileNode.insertTableToTree(tableD) + fileNode.insertTableToTree(tableA, TableType.PRIMITIVE) + fileNode.insertTableToTree(tableB, TableType.PRIMITIVE) + fileNode.insertTableToTree(tableC, TableType.PRIMITIVE) + fileNode.insertTableToTree(tableD, TableType.PRIMITIVE) /** * a b a b c d @@ -41,24 +41,24 @@ class TomlTableTest { * \ \ * a.d.e a.d.e */ - assertEquals( "a", fileNode.findTableInAstByName("a", 1)?.fullTableName,) - assertEquals( "b", fileNode.findTableInAstByName("b", 1)?.fullTableName,) - assertEquals( "c", fileNode.findTableInAstByName("c", 1)?.fullTableName,) - assertEquals( "d", fileNode.findTableInAstByName("d", 1)?.fullTableName,) + assertEquals( "a", fileNode.findTableInAstByName("a", 1, TableType.PRIMITIVE)?.fullTableName,) + assertEquals( "b", fileNode.findTableInAstByName("b", 1, TableType.PRIMITIVE)?.fullTableName,) + assertEquals( "c", fileNode.findTableInAstByName("c", 1, TableType.PRIMITIVE)?.fullTableName,) + assertEquals( "d", fileNode.findTableInAstByName("d", 1, TableType.PRIMITIVE)?.fullTableName,) } @Test fun insertComplexLevelTableToTreeTest() { val fileNode = prepareTree() - val tableA = TomlTable("[a.c]", 0) - val tableB = TomlTable("[b.a.a.a]", 0) - val tableC = TomlTable("[c.a.b]", 0) - val tableD = TomlTable("[d.e.f]", 0) + val tableA = TomlTablePrimitive("[a.c]", 0) + val tableB = TomlTablePrimitive("[b.a.a.a]", 0) + val tableC = TomlTablePrimitive("[c.a.b]", 0) + val tableD = TomlTablePrimitive("[d.e.f]", 0) - fileNode.insertTableToTree(tableA) - fileNode.insertTableToTree(tableB) - fileNode.insertTableToTree(tableC) - fileNode.insertTableToTree(tableD) + fileNode.insertTableToTree(tableA, TableType.PRIMITIVE) + fileNode.insertTableToTree(tableB, TableType.PRIMITIVE) + fileNode.insertTableToTree(tableC, TableType.PRIMITIVE) + fileNode.insertTableToTree(tableD, TableType.PRIMITIVE) /** * a b a b c d @@ -70,14 +70,14 @@ class TomlTableTest { * b.a.a.a */ - assertEquals( "a.c", fileNode.findTableInAstByName("a.c", 2)?.fullTableName,) - assertEquals( "b.a.a.a", fileNode.findTableInAstByName("b.a.a.a", 4)?.fullTableName,) - assertEquals( "c.a.b", fileNode.findTableInAstByName("c.a.b", 3)?.fullTableName,) - assertEquals( "d.e.f", fileNode.findTableInAstByName("d.e.f", 3)?.fullTableName,) + assertEquals( "a.c", fileNode.findTableInAstByName("a.c", 2, TableType.PRIMITIVE)?.fullTableName,) + assertEquals( "b.a.a.a", fileNode.findTableInAstByName("b.a.a.a", 4, TableType.PRIMITIVE)?.fullTableName,) + assertEquals( "c.a.b", fileNode.findTableInAstByName("c.a.b", 3, TableType.PRIMITIVE)?.fullTableName,) + assertEquals( "d.e.f", fileNode.findTableInAstByName("d.e.f", 3, TableType.PRIMITIVE)?.fullTableName,) // checking that table [b.a.a.a] is a node in a tree that does not have children and it's grandparent is [b] - val baaaNode = fileNode.findTableInAstByName("b.a.a.a", 4)!! - val ba = fileNode.findTableInAstByName("b", 1)!! + val baaaNode = fileNode.findTableInAstByName("b.a.a.a", 4, TableType.PRIMITIVE)!! + val ba = fileNode.findTableInAstByName("b", 1, TableType.PRIMITIVE)!! assertEquals(emptySet(), baaaNode.children) assertEquals(ba, baaaNode.parent?.parent?.parent) } @@ -91,13 +91,13 @@ class TomlTableTest { */ fun prepareTree(): TomlFile { val fileNode = TomlFile() - val sectionA = TomlTable("[a]", 0) - val sectionAC = TomlTable("[a.c]", 1) - val sectionAD = TomlTable("[a.d]", 2) - val sectionADE = TomlTable("[a.d.e]", 3) + val sectionA = TomlTablePrimitive("[a]", 0) + val sectionAC = TomlTablePrimitive("[a.c]", 1) + val sectionAD = TomlTablePrimitive("[a.d]", 2) + val sectionADE = TomlTablePrimitive("[a.d.e]", 3) - val sectionB = TomlTable("[b]", 4) - val sectionBA = TomlTable("[b.a]", 5) + val sectionB = TomlTablePrimitive("[b]", 4) + val sectionBA = TomlTablePrimitive("[b.a]", 5) fileNode.appendChild(sectionA) fileNode.appendChild(sectionB) diff --git a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/ValueParserTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ValueParserTest.kt similarity index 99% rename from ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/ValueParserTest.kt rename to ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ValueParserTest.kt index 3372863c..7ac3a732 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akulashov7/ktoml/parsers/ValueParserTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/ValueParserTest.kt @@ -1,4 +1,4 @@ -package com.akulashov7.ktoml.parsers +package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.exceptions.ParseException import com.akuleshov7.ktoml.tree.* diff --git a/ktoml-file/build.gradle.kts b/ktoml-file/build.gradle.kts index 587c61b5..2a93d6e3 100644 --- a/ktoml-file/build.gradle.kts +++ b/ktoml-file/build.gradle.kts @@ -1,5 +1,4 @@ import com.akuleshov7.buildutils.configurePublishing -import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentOperatingSystem import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest plugins { diff --git a/ktoml-file/src/commonTest/kotlin/com/akulashov7/ktoml/file/TomlFileParserTest.kt b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt similarity index 96% rename from ktoml-file/src/commonTest/kotlin/com/akulashov7/ktoml/file/TomlFileParserTest.kt rename to ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt index aabb538e..1715a789 100644 --- a/ktoml-file/src/commonTest/kotlin/com/akulashov7/ktoml/file/TomlFileParserTest.kt +++ b/ktoml-file/src/commonTest/kotlin/com/akuleshov7/ktoml/file/TomlFileParserTest.kt @@ -1,10 +1,8 @@ -package com.akulashov7.ktoml.file +package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.* -import com.akuleshov7.ktoml.file.TomlFileReader -import com.akuleshov7.ktoml.file.readAndParseFile import com.akuleshov7.ktoml.parsers.TomlParser -import com.akuleshov7.ktoml.tree.TomlTable +import com.akuleshov7.ktoml.tree.TomlTablePrimitive import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable import kotlinx.serialization.serializer @@ -199,7 +197,7 @@ class TomlFileParserTest { TomlParser(TomlConfig()) .parseStringsToTomlTree(lines, TomlConfig()) .children - .filterIsInstance() + .filterIsInstance() .filter { !it.isSynthetic } .map { it.fullTableName } )