From 76bcae0414d593651b4ae40549b2636f10d9490a Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Sun, 7 May 2023 22:34:20 +0300 Subject: [PATCH] Several cosmetic fixes for multiline strings (#217) ### What's done: - On multiline strings we forgot to update isMultiline flag while decoding and parsing - Adding extra info to prettyPrint - Adding a test for validation of line numbers - Removing deprecated 'content' from the TomlNode - Added newline alignment for multiline strings (newlines after/before quotes-separators) --- .../ktoml/decoders/TomlMainDecoder.kt | 4 +- .../akuleshov7/ktoml/tree/nodes/TomlNode.kt | 67 +++++---------- .../nodes/pairs/values/TomlLiteralString.kt | 4 +- .../akuleshov7/ktoml/writers/TomlEmitter.kt | 2 + .../ktoml/encoders/EncodingAnnotationTest.kt | 6 ++ .../ktoml/encoders/PrimitiveEncoderTest.kt | 33 +++++++ .../akuleshov7/ktoml/parsers/SetLineNoTest.kt | 86 +++++++++++++++++++ 7 files changed, 151 insertions(+), 51 deletions(-) create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/SetLineNoTest.kt 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 a2fcbaec..7ccc7ed5 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 @@ -101,10 +101,10 @@ public class TomlMainDecoder( is TomlKeyValuePrimitive -> node 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 + // branch, we should throw an exception as it is not expected at all, and we should catch this in tests else -> throw InternalDecodingException( - "Node of type [${node::class}] should not be processed in TomlDecoder.decodeValue(): <${node.content}>." + "Node of type [${node::class}] should not be processed in TomlDecoder.decodeValue(): <$node>." ) } } diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlNode.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlNode.kt index 3a73f835..f808fbb7 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlNode.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlNode.kt @@ -29,13 +29,6 @@ public sealed class TomlNode( 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. */ @@ -187,20 +180,25 @@ public sealed class TomlNode( /** * print the structure of parsed AST tree + * Important: as prettyPrint calls toString() of the node, and not just prints the value, but emits and reconstruct a source string, + * so in some cases (for example in case of multiline strings) it can work incorrectly. + * + * @param emitLine - if true - will print line number in this debug print */ @Suppress("DEBUG_PRINT") - public fun prettyPrint() { + public fun prettyPrint(emitLine: Boolean = false) { val sb = StringBuilder() - prettyPrint(this, sb) + prettyPrint(this, sb, emitLine) println(sb.toString()) } /** + * @param emitLine - if true - will print line number in this debug print * @return the string with AST tree visual representation */ - public fun prettyStr(): String { + public fun prettyStr(emitLine: Boolean = false): String { val sb = StringBuilder() - prettyPrint(this, sb) + prettyPrint(this, sb, emitLine) return sb.toString() } @@ -233,37 +231,6 @@ 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]. - * - * @param emitter The [TomlEmitter] instance to write to. - * @param config The [TomlConfig] instance. Defaults to the node's config. - * @param multiline Whether to write the node over multiple lines, if possible. - */ - @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: TomlOutputConfig = TomlOutputConfig(), - multiline: Boolean = false - ): Unit = write(emitter, config) - /** * Writes this node as text to [emitter]. * @@ -328,6 +295,9 @@ public sealed class TomlNode( .writeNode(this) .replace(" = ", "=") + internal fun print(emitLine: Boolean = false): String = + "${this::class.simpleName} ($this)${if (emitLine) "[line:${this.lineNo}]" else ""}\n" + public companion object { // number of spaces that is used to indent levels internal const val INDENTING_LEVEL = 4 @@ -335,19 +305,22 @@ public sealed class TomlNode( /** * recursive print the tree using the current node * - * @param node - * @param level - * @param result + * @param node that will be printed + * @param level depth of hierarchy for print + * @param result string builder where the result is stored + * @param emitLine if true - will print line number in this debug print */ public fun prettyPrint( node: TomlNode, result: StringBuilder, + emitLine: Boolean = false, level: Int = 0 ) { val spaces = " ".repeat(INDENTING_LEVEL * level) - result.append("$spaces - ${node::class.simpleName} ($node)\n") + // we are using print() method here instead of toString() + result.append("$spaces - ${node.print(emitLine)}") node.children.forEach { child -> - prettyPrint(child, result, level + 1) + prettyPrint(child, result, emitLine, level + 1) } } } 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 index d5504e95..c9f8188b 100644 --- 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 @@ -28,7 +28,7 @@ public class TomlLiteralString internal constructor( content: String, lineNo: Int, config: TomlInputConfig = TomlInputConfig() - ) : this(content.verifyAndTrimQuotes(lineNo, config)) + ) : this(content.verifyAndTrimQuotes(lineNo, config), content.contains("\n")) @Deprecated( message = "TomlConfig is deprecated; use TomlInputConfig instead. Will be removed in next releases." @@ -45,7 +45,7 @@ public class TomlLiteralString internal constructor( override fun write( emitter: TomlEmitter, - config: TomlOutputConfig + config: TomlOutputConfig, ) { val content = content as String 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 1ab6fe98..89397573 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 @@ -180,7 +180,9 @@ public abstract class TomlEmitter(config: TomlOutputConfig) { val quotes = if (isLiteral) "'''" else "\"\"\"" emit(quotes) + .emitNewLine() .emit(string) + .emitNewLine() .emit(quotes) } else { val quote = if (isLiteral) '\'' else '"' 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 index 3147c7b8..4a6ede5c 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/EncodingAnnotationTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/EncodingAnnotationTest.kt @@ -269,16 +269,22 @@ class EncodingAnnotationTest { 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/PrimitiveEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PrimitiveEncoderTest.kt index a2e1d3a9..7f9644be 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PrimitiveEncoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PrimitiveEncoderTest.kt @@ -1,6 +1,7 @@ package com.akuleshov7.ktoml.encoders import com.akuleshov7.ktoml.annotations.TomlLiteral +import com.akuleshov7.ktoml.annotations.TomlMultiline import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.test.Test @@ -81,6 +82,38 @@ class PrimitiveEncoderTest { ) } + @Test + fun multilineStringsSpecifications() { + @Serializable + data class MultilineLiteralStr( + @TomlMultiline + @TomlLiteral + val a: String + ) + + @Serializable + data class MultilineBasicStr( + @TomlMultiline + val a: String + ) + + assertEncodedEquals( + value = MultilineLiteralStr("test \n test \n test \'\'\'"), + expectedToml = """ + |a = ''' + |test + | test + | test ''\' + |''' + """.trimMargin() + ) + + assertEncodedEquals( + value = MultilineBasicStr("test \n test \n test \'\'\'"), + expectedToml = "a = \"\"\"\ntest \n test \n test \'\'\'\n\"\"\"" + ) + } + @Test fun jsWholeDoubleRegression() { @Serializable diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/SetLineNoTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/SetLineNoTest.kt new file mode 100644 index 00000000..5e49da51 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/SetLineNoTest.kt @@ -0,0 +1,86 @@ +package com.akuleshov7.ktoml.parsers + +import com.akuleshov7.ktoml.Toml +import kotlin.test.Test +import kotlin.test.assertEquals + +class SetLineNoTest { + @Test + fun checkingLineNumbersGeneral() { + val string = + """ + + # comment 1 + + [a] # comment 2 + # comment 3 + test = 1 # comment 4 + + # ==== + + [[a.b]] # comment 5 + test = 1 + + mlls = ''' + 1 + 2 + 3 + ''' + + mla = [ + "a", + "b", + "c" + ] + """.trimIndent() + val parsedToml = Toml.tomlParser.parseString(string) + + parsedToml.prettyPrint(true) + assertEquals( + """ + | - TomlFile (rootNode)[line:0] + | - TomlTablePrimitive ([a])[line:4] + | - TomlKeyValuePrimitive (test=1)[line:6] + | - TomlArrayOfTables ([[a.b]])[line:10] + | - TomlArrayOfTablesElement (technical_node)[line:10] + | - TomlKeyValuePrimitive (test=1)[line:11] + | - TomlKeyValuePrimitive (mlls=''' + | 1 + | 2 + | 3 + | + |''')[line:13] + | - TomlKeyValueArray (mla=[ "a", "b", "c" ])[line:18] + | + """.trimMargin(), + parsedToml.prettyStr(true) + ) + } + + @Test + fun checkingLineNumbers() { + val string = "\n\n" + + "mlls = '''\n" + + "1\n" + + "\n" + + "2\n" + + "3" + + "'''" + val parsedToml = Toml.tomlParser.parseString(string) + parsedToml.prettyPrint(true) + + assertEquals( + """ + | - TomlFile (rootNode)[line:0] + | - TomlKeyValuePrimitive (mlls=''' + |1 + | + |2 + |3 + |''')[line:3] + | + """.trimMargin(), + parsedToml.prettyStr(true) + ) + } +}