Skip to content

Commit

Permalink
Store comments in the tree (#140)
Browse files Browse the repository at this point in the history
* Add a config option for ignoring comments

* Add "comments" and "inlineComment" properties to tree nodes

* Implement comment writing

* Add tests and fix failures

* Remove the ignoreComments config property

* Simplify comment parsing functions

Replaced occurrences of findBeginningOfTheComment and substring with
takeBeforeComment, and trimCommentLine with trimComment.

* Move comment list copying to the node constructor

* Fix test failures on comment functions
  • Loading branch information
NightEule5 authored May 20, 2022
1 parent 69ae8fc commit 443d8d3
Show file tree
Hide file tree
Showing 18 changed files with 236 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public class TomlArrayDecoder(
rootNode.key,
currentPrimitiveElementOfArray,
rootNode.lineNo,
comments = emptyList(),
inlineComment = "",
rootNode.key.content,
config
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,34 @@ package com.akuleshov7.ktoml.parsers
import com.akuleshov7.ktoml.exceptions.ParseException

/**
* method to find the beginning of the comments in TOML string
* Takes only the text before a comment, searching for a comment after the specified
* [startIndex].
*
* @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
* @param startIndex The index to start the comment search from.
* @return The text before a comment, i.e.
* ```kotlin
* "a = 0 # Comment".takeBeforeComment() == "a = 0"
* ```
*/
internal fun String.findBeginningOfTheComment(startSearchFrom: Int) =
(startSearchFrom until this.length).filter { this[it] == '#' }.minOrNull() ?: this.length
internal fun String.takeBeforeComment(startIndex: Int) =
when (val hashIndex = indexOf('#', startIndex)) {
-1 -> trim()
else -> take(hashIndex).trim()
}

/**
* Trims a comment of any text before it and its hash token.
*
* @return The comment text, i.e.
* ```kotlin
* "a = 0 # Comment".trimComment() == "Comment"
* ```
*/
internal fun String.trimComment() =
when (val hashIndex = indexOf('#')) {
-1 -> ""
else -> drop(hashIndex + 1).trim()
}

/**
* Splitting dot-separated string to the list of tokens:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,25 @@ 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<String> = mutableListOf()

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.isComment()) {
comments += line.trimComment()
} else if (!line.isEmptyLine()) {
// Parse the inline comment if any
val inlineComment = line.trimComment()

if (line.isTableNode()) {
if (line.isArrayOfTables()) {
// TomlArrayOfTables contains all information about the ArrayOfTables ([[array of tables]])
val tableArray = TomlArrayOfTables(line, lineNo, config)
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, config)
// 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)
Expand All @@ -62,7 +69,7 @@ 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(line, lineNo, comments, inlineComment, 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) {
Expand All @@ -74,7 +81,7 @@ public value class TomlParser(private val config: TomlConfig) {
currentParentalNode = tomlFileHead.insertTableToTree(tableSection)
}
} else {
val keyValue = line.parseTomlKeyValue(lineNo, config)
val keyValue = line.parseTomlKeyValue(lineNo, comments, inlineComment, config)
// inserting the key-value record to the tree
when {
keyValue is TomlKeyValue && keyValue.key.isDotted ->
Expand All @@ -93,6 +100,8 @@ public value class TomlParser(private val config: TomlConfig) {
else -> currentParentalNode.appendChild(keyValue)
}
}

comments.clear()
}
}
return tomlFileHead
Expand Down Expand Up @@ -134,14 +143,21 @@ public value class TomlParser(private val config: TomlConfig) {
* 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<String>,
inlineComment: String,
config: TomlConfig
): 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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ 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.takeBeforeComment
import com.akuleshov7.ktoml.parsers.trimDoubleBrackets
import com.akuleshov7.ktoml.parsers.trimQuotes
import com.akuleshov7.ktoml.writers.TomlEmitter
Expand All @@ -26,6 +26,8 @@ public class TomlArrayOfTables(
) : TomlTable(
content,
lineNo,
comments = emptyList(),
inlineComment = "",
config,
isSynthetic
) {
Expand All @@ -47,11 +49,8 @@ public class TomlArrayOfTables(
" 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()
val sectionFromContent = content.takeBeforeComment(lastIndexOfBrace).trimDoubleBrackets()
.trim()

if (sectionFromContent.isBlank()) {
Expand Down Expand Up @@ -89,7 +88,9 @@ public class TomlArrayOfTables(
emitIndent()
}

writeChildComments(child)
writeHeader(headerKey, config)
writeChildInlineComment(child)

if (!child.hasNoChildren()) {
emitNewLine()
Expand Down Expand Up @@ -120,9 +121,16 @@ public class TomlArrayOfTables(
/**
* 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(
public class TomlArrayOfTablesElement(
lineNo: Int,
comments: List<String>,
inlineComment: String,
config: TomlConfig = TomlConfig()
) : TomlNode(
EMPTY_TECHNICAL_NODE,
lineNo,
comments,
inlineComment,
config
) {
override val name: String = EMPTY_TECHNICAL_NODE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import com.akuleshov7.ktoml.writers.TomlEmitter
public class TomlFile(config: TomlConfig = TomlConfig()) : TomlNode(
"rootNode",
0,
comments = emptyList(),
inlineComment = "",
config
) {
override val name: String = "rootNode"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ import com.akuleshov7.ktoml.writers.TomlEmitter
public class TomlInlineTable(
private val keyValuePair: Pair<String, String>,
lineNo: Int,
comments: List<String> = emptyList(),
inlineComment: String = "",
config: TomlConfig = TomlConfig(),
) : TomlNode(
"${keyValuePair.first} = ${keyValuePair.second}",
lineNo,
comments,
inlineComment,
config
) {
override val name: String = keyValuePair.first
Expand All @@ -40,7 +44,7 @@ public class TomlInlineTable(
}
}
.split(",")
.map { it.parseTomlKeyValue(lineNo, config) }
.map { it.parseTomlKeyValue(lineNo, comments = emptyList(), inlineComment = "", config) }

return parsedList
}
Expand All @@ -49,6 +53,8 @@ public class TomlInlineTable(
val tomlTable = TomlTablePrimitive(
"[${if (currentParentalNode is TomlTable) "${currentParentalNode.fullTableName}." else ""}${keyValuePair.first}]",
lineNo,
comments,
inlineComment,
config
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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.takeBeforeComment
import com.akuleshov7.ktoml.writers.TomlEmitter

private typealias ValueCreator = (String, Int) -> TomlValue
Expand All @@ -14,6 +14,8 @@ internal interface TomlKeyValue {
var key: TomlKey
val value: TomlValue
val lineNo: Int
val comments: List<String>
val inlineComment: String

/**
* this is a small hack to support dotted keys
Expand All @@ -39,6 +41,8 @@ internal interface TomlKeyValue {
return TomlTablePrimitive(
"[$parentalPrefix${syntheticTablePrefix.joinToString(".")}]",
lineNo,
comments,
inlineComment,
config,
true
)
Expand Down Expand Up @@ -83,12 +87,12 @@ public fun String.splitKeyValue(lineNo: Int, config: TomlConfig = TomlConfig()):
this.lastIndexOf("\"\"\"")
).filterNot { it == -1 }.maxOrNull() ?: 0

// finding the index of a commented part of the string
// removing the commented part of the string
// search starts goes from the closingQuoteIndex to the end of the string
val firstHash = this.findBeginningOfTheComment(closingQuoteIndex)
val pair = takeBeforeComment(closingQuoteIndex)

// 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) {
Expand All @@ -101,8 +105,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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,31 @@ public class TomlKeyValueArray(
override var key: TomlKey,
override val value: TomlValue,
override val lineNo: Int,
comments: List<String>,
inlineComment: String,
override val name: String,
config: TomlConfig = TomlConfig()
) : TomlNode(
key,
value,
lineNo,
comments,
inlineComment,
config
), TomlKeyValue {
// adaptor for a string pair of key-value
public constructor(
keyValuePair: Pair<String, String>,
lineNo: Int,
comments: List<String> = emptyList(),
inlineComment: String = "",
config: TomlConfig = TomlConfig()
) : this(
TomlKey(keyValuePair.first, lineNo),
keyValuePair.second.parseList(lineNo, config),
lineNo,
comments,
inlineComment,
TomlKey(keyValuePair.first, lineNo).content
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,31 @@ public class TomlKeyValuePrimitive(
override var key: TomlKey,
override val value: TomlValue,
override val lineNo: Int,
comments: List<String>,
inlineComment: String,
override val name: String,
config: TomlConfig = TomlConfig()
) : TomlNode(
key,
value,
lineNo,
comments,
inlineComment,
config
), TomlKeyValue {
// adaptor for a string pair of key-value
public constructor(
keyValuePair: Pair<String, String>,
lineNo: Int,
comments: List<String> = emptyList(),
inlineComment: String = "",
config: TomlConfig = TomlConfig()
) : this(
TomlKey(keyValuePair.first, lineNo),
keyValuePair.second.parseValue(lineNo, config),
lineNo,
comments,
inlineComment,
TomlKey(keyValuePair.first, lineNo).content
)

Expand Down
Loading

0 comments on commit 443d8d3

Please sign in to comment.