Skip to content

Commit 2d60279

Browse files
author
Clément Petit
committed
Add support for decoding
1 parent ab93aec commit 2d60279

File tree

4 files changed

+500
-316
lines changed

4 files changed

+500
-316
lines changed

formats/properties/api/kotlinx-serialization-properties.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public abstract class kotlinx/serialization/properties/StringProperties : kotlin
5050
public static final field Default Lkotlinx/serialization/properties/StringProperties$Default;
5151
public synthetic fun <init> (Lkotlinx/serialization/properties/PropertiesConf;Lkotlinx/serialization/properties/Properties;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
5252
public synthetic fun <init> (Lkotlinx/serialization/properties/PropertiesConf;Lkotlinx/serialization/properties/Properties;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
53+
public final fun decodeFromString (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/String;)Ljava/lang/Object;
5354
public final fun encodeToString (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Ljava/lang/String;
5455
public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
5556
}

formats/properties/commonMain/src/kotlinx/serialization/properties/StringProperties.kt

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,48 @@ public sealed class StringProperties(
4040
return builder.toString()
4141
}
4242

43+
/**
44+
* Decodes properties from the given [string] to a value of type [T] using the given [deserializer].
45+
* [String] values are converted to respective primitive types using default conversion methods.
46+
* [T] may contain properties of nullable types; they will be filled by non-null values from the [map], if present.
47+
*/
48+
public fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T {
49+
val result = mutableMapOf<String, String>()
50+
for (line in string.logicalLines()) {
51+
println("line=`$line`")
52+
val parsedLine = line.unescaped()
53+
println("parsedLine=`$parsedLine`")
54+
var keyEnd = parsedLine.length
55+
for (i in parsedLine.indices) {
56+
if (parsedLine[i] in separators) {
57+
keyEnd = i
58+
break
59+
}
60+
}
61+
62+
var valueBegin = parsedLine.length
63+
var separatorFound = false
64+
for (i in keyEnd..parsedLine.lastIndex) {
65+
if (separatorFound && parsedLine[i] != ' ') {
66+
valueBegin = i
67+
break
68+
}
69+
if (parsedLine[i] in nonBlankSeparators) {
70+
separatorFound = true
71+
}
72+
if (parsedLine[i] !in separators) {
73+
valueBegin = i
74+
break
75+
}
76+
}
77+
78+
result[parsedLine.substring(0, keyEnd)] = parsedLine.substring(valueBegin)
79+
}
80+
println(result)
81+
println(result.keys)
82+
return properties.decodeFromStringMap(deserializer, result)
83+
}
84+
4385
/**
4486
* A [Properties] instance that can be used as default and does not have any [SerializersModule] installed.
4587
*/
@@ -70,13 +112,14 @@ public fun StringProperties(builderAction: PropertiesBuilder.() -> Unit = {}): S
70112
public inline fun <reified T> StringProperties.encodeToString(value: T): String =
71113
encodeToString(serializersModule.serializer(), value)
72114

73-
///**
74-
// * Decodes properties from given [propertiesString], assigns them to an object using serializer for reified type [T] and returns this object.
75-
// * [String] values are converted to respective primitive types using default conversion methods.
76-
// * [T] may contain properties of nullable types; they will be filled by non-null values from the [map], if present.
77-
// */
78-
//@ExperimentalSerializationApi
79-
//public inline fun <reified T> StringProperties.decodeFromString(propertiesString: String): T = TODO()
115+
/**
116+
* Decodes properties from given [propertiesString], assigns them to an object using serializer for reified type [T] and returns this object.
117+
* [String] values are converted to respective primitive types using default conversion methods.
118+
* [T] may contain properties of nullable types; they will be filled by non-null values from the [map], if present.
119+
*/
120+
@ExperimentalSerializationApi
121+
public inline fun <reified T> StringProperties.decodeFromString(propertiesString: String): T =
122+
decodeFromString(serializersModule.serializer(), propertiesString)
80123

81124
@ExperimentalSerializationApi
82125
public class PropertiesBuilder internal constructor(from: PropertiesConf) {
@@ -125,3 +168,56 @@ public enum class KeyValueSeparator(private val c: Char) {
125168

126169
public fun char(): Char = c
127170
}
171+
172+
private val nonBlankSeparators = setOf('=', ':')
173+
private val separators = nonBlankSeparators + ' '
174+
private val wellKnownEscapeChars = mapOf(
175+
'\\' to '\\',
176+
'n' to '\n',
177+
'r' to '\r',
178+
't' to '\t'
179+
)
180+
181+
private fun String.unescaped(): String {
182+
val sb = StringBuilder(this.length)
183+
var i = 0
184+
while (i < this.length) {
185+
if (i < this.length - 1 && this[i] == '\\') {
186+
if (this[i + 1] in wellKnownEscapeChars) {
187+
sb.append(wellKnownEscapeChars[this[i + 1]])
188+
i += 2
189+
} else {
190+
i++
191+
}
192+
} else {
193+
sb.append(this[i])
194+
i++
195+
}
196+
}
197+
return sb.toString()
198+
}
199+
200+
private fun String.logicalLines(): List<String> {
201+
val commentFilter = "[ \\t\\f]*[#!].*".toRegex()
202+
val lines = lines()
203+
.filterNot { it.isBlank() || commentFilter.matches(it) }
204+
.toMutableList()
205+
val logicalLines = mutableListOf<String>()
206+
207+
var currentLine = ""
208+
for (line in lines) {
209+
val trimmedLine = line.trimStart()
210+
if (trimmedLine.endsWith("\\")) {
211+
currentLine += trimmedLine.dropLast(1)
212+
} else {
213+
currentLine += trimmedLine
214+
logicalLines.add(currentLine)
215+
currentLine = ""
216+
}
217+
}
218+
if (currentLine.isNotBlank()) {
219+
logicalLines.add(currentLine)
220+
}
221+
222+
return logicalLines
223+
}

0 commit comments

Comments
 (0)