Skip to content

Commit

Permalink
Merge pull request #296 from kitterion/parse-to-yaml-node
Browse files Browse the repository at this point in the history
Add parsing to YamlNode
  • Loading branch information
charleskorn authored Jun 23, 2022
2 parents 32e2247 + 11ef3a8 commit 804ceac
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 4 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,29 @@ val result = Yaml.default.encodeToString(Team.serializer(), input)
println(result)
```

### Parsing into YamlNode

It is possible to parse a string or an InputStream directly into a YamlNode, for example
the following code prints `Cindy`.
```kotlin
val input = """
leader: Amy
members:
- Bob
- Cindy
- Dan
""".trimIndent()

val result = Yaml.default.parseToYamlNode(input)

println(
result
.yamlMap.get<YamlList>("members")!![1]
.yamlScalar
.content
)
```

## Referencing kaml

Add the following to your Gradle build script:
Expand Down
21 changes: 21 additions & 0 deletions src/commonMain/kotlin/com/charleskorn/kaml/YamlNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ public data class YamlList(val items: List<YamlNode>, override val path: YamlPat
return this.items.zip(other.items).all { (mine, theirs) -> mine.equivalentContentTo(theirs) }
}

public operator fun get(index: Int): YamlNode = items[index]

override fun contentToString(): String = "[" + items.joinToString(", ") { it.contentToString() } + "]"

override fun withPath(newPath: YamlPath): YamlList {
Expand Down Expand Up @@ -248,3 +250,22 @@ public data class YamlTaggedNode(val tag: String, val innerNode: YamlNode) : Yam

override fun toString(): String = "tagged '$tag': $innerNode"
}

public val YamlNode.yamlScalar: YamlScalar
get() = this as? YamlScalar ?: error(this, "YamlScalar")

public val YamlNode.yamlNull: YamlNull
get() = this as? YamlNull ?: error(this, "YamlNull")

public val YamlNode.yamlList: YamlList
get() = this as? YamlList ?: error(this, "YamlList")

public val YamlNode.yamlMap: YamlMap
get() = this as? YamlMap ?: error(this, "YamlMap")

public val YamlNode.yamlTaggedNode: YamlTaggedNode
get() = this as? YamlTaggedNode ?: error(this, "YamlTaggedNode")

private fun error(node: YamlNode, expectedType: String): Nothing {
throw IncorrectTypeException("Expected element to be $expectedType but is ${node::class.simpleName}", node.path)
}
28 changes: 28 additions & 0 deletions src/commonTest/kotlin/com/charleskorn/kaml/YamlListTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.charleskorn.kaml

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe

Expand Down Expand Up @@ -75,6 +76,33 @@ class YamlListTest : DescribeSpec({
}
}

describe("getting elements from a list") {
val firstItemPath = YamlPath.root.withListEntry(0, Location(4, 5))
val secondItemPath = YamlPath.root.withListEntry(1, Location(6, 7))

val list = YamlList(
listOf(
YamlScalar("item 1", firstItemPath),
YamlScalar("item 2", secondItemPath)
),
YamlPath.root
)

describe("getting element in bounds") {
it("returns element") {
list[0] shouldBe YamlScalar("item 1", firstItemPath)
list[1] shouldBe YamlScalar("item 2", secondItemPath)
}
}

describe("getting element out of bounds") {
it("throws IndexOutOfBoundsException") {
shouldThrow<IndexOutOfBoundsException> { list[2] }
shouldThrow<IndexOutOfBoundsException> { list[10] }
}
}
}

describe("converting the content to a human-readable string") {
context("an empty list") {
val list = YamlList(emptyList(), YamlPath.root)
Expand Down
82 changes: 82 additions & 0 deletions src/commonTest/kotlin/com/charleskorn/kaml/YamlNodeTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
Copyright 2018-2021 Charles Korn.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package com.charleskorn.kaml

import io.kotest.assertions.asClue
import io.kotest.assertions.throwables.shouldNotThrowAny
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe

class YamlNodeTest : DescribeSpec({
describe("converting from YamlNode") {
val path = YamlPath.root

val testScalar = YamlScalar("test", path)
val testNull = YamlNull(path)
val testList = YamlList(emptyList(), path)
val testMap = YamlMap(emptyMap(), path)
val testTaggedNode = YamlTaggedNode("tag", YamlScalar("tagged_scalar", path))

listOf(
Triple("YamlScalar", YamlNode::yamlScalar, testScalar),
Triple("YamlNull", YamlNode::yamlNull, testNull),
Triple("YamlList", YamlNode::yamlList, testList),
Triple("YamlMap", YamlNode::yamlMap, testMap),
Triple("YamlTaggedNode", YamlNode::yamlTaggedNode, testTaggedNode),
).forEach { (type, method, value) ->
it("successfully converts to $type") {
shouldNotThrowAny { method(value) }
method(value) shouldBe value
}
}

listOf(
Triple("YamlScalar", YamlNode::yamlScalar, testNull),
Triple("YamlScalar", YamlNode::yamlScalar, testList),
Triple("YamlScalar", YamlNode::yamlScalar, testMap),
Triple("YamlScalar", YamlNode::yamlScalar, testTaggedNode),
Triple("YamlNull", YamlNode::yamlNull, testScalar),
Triple("YamlNull", YamlNode::yamlNull, testList),
Triple("YamlNull", YamlNode::yamlNull, testMap),
Triple("YamlNull", YamlNode::yamlNull, testTaggedNode),
Triple("YamlList", YamlNode::yamlList, testScalar),
Triple("YamlList", YamlNode::yamlList, testNull),
Triple("YamlList", YamlNode::yamlList, testMap),
Triple("YamlList", YamlNode::yamlList, testTaggedNode),
Triple("YamlMap", YamlNode::yamlMap, testScalar),
Triple("YamlMap", YamlNode::yamlMap, testNull),
Triple("YamlMap", YamlNode::yamlMap, testList),
Triple("YamlMap", YamlNode::yamlMap, testTaggedNode),
Triple("YamlTaggedNode", YamlNode::yamlTaggedNode, testScalar),
Triple("YamlTaggedNode", YamlNode::yamlTaggedNode, testNull),
Triple("YamlTaggedNode", YamlNode::yamlTaggedNode, testList),
Triple("YamlTaggedNode", YamlNode::yamlTaggedNode, testMap),
).forEach { (type, method, value) ->
val fromType = value::class.simpleName
it("throws when converting from $fromType to $type") {
val exception = shouldThrow<IncorrectTypeException> { method(value) }
exception.asClue {
it.message shouldBe "Expected element to be $type but is $fromType"
it.path shouldBe path
}
}
}
}
})
17 changes: 13 additions & 4 deletions src/jvmMain/kotlin/com/charleskorn/kaml/Yaml.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,24 @@ public actual class Yaml(
}

private fun <T> decodeFromReader(deserializer: DeserializationStrategy<T>, source: Reader): T {
val parser = YamlParser(source)
val reader = YamlNodeReader(parser, configuration.extensionDefinitionPrefix)
val rootNode = reader.read()
parser.ensureEndOfStreamReached()
val rootNode = parseToYamlNodeFromReader(source)

val input = YamlInput.createFor(rootNode, serializersModule, configuration, deserializer.descriptor)
return input.decodeSerializableValue(deserializer)
}

public fun parseToYamlNode(string: String): YamlNode = parseToYamlNodeFromReader(StringReader(string))

public fun parseToYamlNode(source: InputStream): YamlNode = parseToYamlNodeFromReader(InputStreamReader(source))

private fun parseToYamlNodeFromReader(source: Reader): YamlNode {
val parser = YamlParser(source)
val reader = YamlNodeReader(parser, configuration.extensionDefinitionPrefix)
val node = reader.read()
parser.ensureEndOfStreamReached()
return node
}

override fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String {
val writer = object : StringWriter(), StreamDataWriter {
override fun flush() { }
Expand Down
18 changes: 18 additions & 0 deletions src/jvmTest/kotlin/com/charleskorn/kaml/JvmYamlReadingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,23 @@ class JvmYamlReadingTest : DescribeSpec({
result shouldBe 123
}
}

describe("parsing into a YamlNode from a string") {
val input = "123"
val result = Yaml.default.parseToYamlNode(input)

it("successfully deserializes values from a string") {
result shouldBe YamlScalar("123", YamlPath.root)
}
}

describe("parsing into a YamlNode from a stream") {
val input = "123"
val result = Yaml.default.parseToYamlNode(ByteArrayInputStream(input.toByteArray(Charsets.UTF_8)))

it("successfully deserializes values from a stream") {
result shouldBe YamlScalar("123", YamlPath.root)
}
}
}
})

0 comments on commit 804ceac

Please sign in to comment.