Skip to content

Commit

Permalink
Merge pull request #371 from russellbanks/add-plain-except-ambiguous
Browse files Browse the repository at this point in the history
Add SingleLineStringStyle.PlainExceptAmbiguous and AmbiguousEscapeStyle
  • Loading branch information
charleskorn authored Jan 28, 2023
2 parents f40ac61 + d80f1e0 commit f4e94d1
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 0 deletions.
17 changes: 17 additions & 0 deletions src/commonMain/kotlin/com/charleskorn/kaml/YamlConfiguration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ package com.charleskorn.kaml
* * [encodingIndentationSize]: number of spaces to use as indentation when encoding objects as YAML
* * [breakScalarsAt]: maximum length of scalars when encoding objects as YAML (scalars exceeding this length will be split into multiple lines)
* * [sequenceStyle]: how sequences (aka lists and arrays) should be formatted. See [SequenceStyle] for an example of each
* * [ambiguousQuoteStyle]: how strings should be escaped when [singleLineStringStyle] is [SingleLineStringStyle.PlainExceptAmbiguous] and the value is ambiguous
* * [sequenceBlockIndent]: number of spaces to use as indentation for sequences, if [sequenceStyle] set to [SequenceStyle.Block]
*/
public data class YamlConfiguration constructor(
Expand All @@ -44,6 +45,7 @@ public data class YamlConfiguration constructor(
internal val sequenceStyle: SequenceStyle = SequenceStyle.Block,
internal val singleLineStringStyle: SingleLineStringStyle = SingleLineStringStyle.DoubleQuoted,
internal val multiLineStringStyle: MultiLineStringStyle = singleLineStringStyle.multiLineStringStyle,
internal val ambiguousQuoteStyle: AmbiguousQuoteStyle = AmbiguousQuoteStyle.DoubleQuoted,
internal val sequenceBlockIndent: Int = 0,
)

Expand Down Expand Up @@ -83,12 +85,27 @@ public enum class SingleLineStringStyle {
DoubleQuoted,
SingleQuoted,
Plain,

/**
* This is the same as [SingleLineStringStyle.Plain], except strings that could be misinterpreted as other types
* will be quoted with the escape style defined in [AmbiguousQuoteStyle].
*
* For example, the strings "True", "0xAB", "1" and "1.2" would all be quoted,
* while "1.2.3" and "abc" would not be quoted.
*/
PlainExceptAmbiguous,
;

public val multiLineStringStyle: MultiLineStringStyle
get() = when (this) {
DoubleQuoted -> MultiLineStringStyle.DoubleQuoted
SingleQuoted -> MultiLineStringStyle.SingleQuoted
Plain -> MultiLineStringStyle.Plain
PlainExceptAmbiguous -> MultiLineStringStyle.Plain
}
}

public enum class AmbiguousQuoteStyle {
DoubleQuoted,
SingleQuoted,
}
95 changes: 95 additions & 0 deletions src/commonTest/kotlin/com/charleskorn/kaml/YamlWritingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,101 @@ class YamlWritingTest : DescribeSpec({
""".trimMargin()
}
}

context("serializing a string with the value of an integer using SingleLineStringStyle.PlainExceptAmbiguous") {
val output = Yaml(configuration = YamlConfiguration(singleLineStringStyle = SingleLineStringStyle.PlainExceptAmbiguous)).encodeToString(String.serializer(), "12")

it("returns the value serialized in the expected YAML form, escaping the integer") {
output shouldBe """"12""""
}
}

context("serializing a string with the value of a boolean using SingleLineStringStyle.PlainExceptAmbiguous") {
val output = Yaml(configuration = YamlConfiguration(singleLineStringStyle = SingleLineStringStyle.PlainExceptAmbiguous)).encodeToString(String.serializer(), "true")

it("returns the value serialized in the expected YAML form, escaping the boolean") {
output shouldBe """"true""""
}
}

context("serializing a string with the value of an float using SingleLineStringStyle.PlainExceptAmbiguous") {
val output = Yaml(configuration = YamlConfiguration(singleLineStringStyle = SingleLineStringStyle.PlainExceptAmbiguous)).encodeToString(String.serializer(), "1.2")

it("returns the value serialized in the expected YAML form, escaping the float") {
output shouldBe """"1.2""""
}
}

context("serializing an unambiguous numerical string using SingleLineStringStyle.PlainExceptAmbiguous") {
val output = Yaml(configuration = YamlConfiguration(singleLineStringStyle = SingleLineStringStyle.PlainExceptAmbiguous)).encodeToString(String.serializer(), "1.2.3")

it("returns the value serialized in the expected YAML form, without being escaped") {
output shouldBe "1.2.3"
}
}

context("serializing an int using SingleLineStringStyle.PlainExceptAmbiguous") {
val output = Yaml(configuration = YamlConfiguration(singleLineStringStyle = SingleLineStringStyle.PlainExceptAmbiguous)).encodeToString(Int.serializer(), 123)

it("returns the value serialized in the expected YAML form, without being escaped") {
output shouldBe "123"
}
}

context("serializing a float using SingleLineStringStyle.PlainExceptAmbiguous") {
val output = Yaml(configuration = YamlConfiguration(singleLineStringStyle = SingleLineStringStyle.PlainExceptAmbiguous)).encodeToString(Float.serializer(), 1.2f)

it("returns the value serialized in the expected YAML form, without being escaped") {
output shouldBe "1.2"
}
}

context("serializing a boolean using SingleLineStringStyle.PlainExceptAmbiguous") {
val output = Yaml(configuration = YamlConfiguration(singleLineStringStyle = SingleLineStringStyle.PlainExceptAmbiguous)).encodeToString(Boolean.serializer(), true)

it("returns the value serialized in the expected YAML form, without being escaped") {
output shouldBe "true"
}
}

context("serializing a string with the value of an integer using SingleLineStringStyle.PlainExceptAmbiguous, escaping with single-quotes") {
val output = Yaml(
configuration = YamlConfiguration(
singleLineStringStyle = SingleLineStringStyle.PlainExceptAmbiguous,
ambiguousQuoteStyle = AmbiguousQuoteStyle.SingleQuoted,
),
).encodeToString(String.serializer(), "12")

it("returns the value serialized in the expected YAML form, escaping the integer with single-quotes") {
output shouldBe """'12'"""
}
}

context("serializing a string with the value of a boolean using SingleLineStringStyle.PlainExceptAmbiguous, escaping with single-quotes") {
val output = Yaml(
configuration = YamlConfiguration(
singleLineStringStyle = SingleLineStringStyle.PlainExceptAmbiguous,
ambiguousQuoteStyle = AmbiguousQuoteStyle.SingleQuoted,
),
).encodeToString(String.serializer(), "true")

it("returns the value serialized in the expected YAML form, escaping the boolean with single-quotes") {
output shouldBe """'true'"""
}
}

context("serializing a string with the value of an float using SingleLineStringStyle.PlainExceptAmbiguous, escaping with single-quotes") {
val output = Yaml(
configuration = YamlConfiguration(
singleLineStringStyle = SingleLineStringStyle.PlainExceptAmbiguous,
ambiguousQuoteStyle = AmbiguousQuoteStyle.SingleQuoted,
),
).encodeToString(String.serializer(), "1.2")

it("returns the value serialized in the expected YAML form, escaping the float with single-quotes") {
output shouldBe """'1.2'"""
}
}
}

describe("serializing enumeration values") {
Expand Down
23 changes: 23 additions & 0 deletions src/jvmMain/kotlin/com/charleskorn/kaml/YamlOutput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ internal class YamlOutput(
} else {
when {
value.contains('\n') -> emitQuotedScalar(value, configuration.multiLineStringStyle.scalarStyle)
configuration.singleLineStringStyle == SingleLineStringStyle.PlainExceptAmbiguous && value.isAmbiguous() -> emitQuotedScalar(value, configuration.ambiguousQuoteStyle.scalarStyle)
else -> emitQuotedScalar(value, configuration.singleLineStringStyle.scalarStyle)
}
}
Expand Down Expand Up @@ -180,6 +181,21 @@ internal class YamlOutput(
return typeName
}

private fun String.isAmbiguous(): Boolean {
return when {
isEmpty() -> true
toBigIntegerOrNull() != null -> true
startsWith("0x") -> true
startsWith("0o") -> true
toDoubleOrNull() != null -> true
startsWith("#") -> true
else -> this in listOf(
"~", "-", ".inf", ".Inf", ".INF", "-.inf", "-.Inf", "-.INF", ".nan", ".NaN", ".NAN", "-.nan", "-.NaN",
"-.NAN", "null", "Null", "NULL", "true", "True", "TRUE", "false", "False", "FALSE",
)
}
}

private val SequenceStyle.flowStyle: FlowStyle
get() = when (this) {
SequenceStyle.Block -> FlowStyle.BLOCK
Expand All @@ -199,6 +215,13 @@ internal class YamlOutput(
SingleLineStringStyle.DoubleQuoted -> ScalarStyle.DOUBLE_QUOTED
SingleLineStringStyle.SingleQuoted -> ScalarStyle.SINGLE_QUOTED
SingleLineStringStyle.Plain -> ScalarStyle.PLAIN
SingleLineStringStyle.PlainExceptAmbiguous -> ScalarStyle.PLAIN
}

private val AmbiguousQuoteStyle.scalarStyle: ScalarStyle
get() = when (this) {
AmbiguousQuoteStyle.DoubleQuoted -> ScalarStyle.DOUBLE_QUOTED
AmbiguousQuoteStyle.SingleQuoted -> ScalarStyle.SINGLE_QUOTED
}

companion object {
Expand Down

0 comments on commit f4e94d1

Please sign in to comment.