loader = YamlConfigurationLoader.builder()
+ .url(url).build()
+ final ConfigurationNode node = loader.load()
+ assertEquals(expected, node)
+ }
+
+ @Test
+ void testWriteBasicFile(final @TempDir Path tempDir) throws ConfigurateException, IOException {
+ final Path target = tempDir.resolve("write-basic.yml")
+ final ConfigurationNode node = BasicConfigurationNode.root { n ->
+ n.node("mapping", "first").set("hello")
+ n.node("mapping", "second").set("world")
+
+ n.node("list").act { c ->
+ c.appendListNode().set(1)
+ c.appendListNode().set(2)
+ c.appendListNode().set(3)
+ c.appendListNode().set(4)
+ }
+ }
+
+ final YamlConfigurationLoader loader = YamlConfigurationLoader.builder()
+ .path(target)
+ .nodeStyle(NodeStyle.BLOCK)
+ .build()
+
+ loader.save(node)
+
+ assertEquals(
+ getClass().getResource("write-expected.yml").getText(StandardCharsets.UTF_8.name()).replace("\r\n", "\n"),
+ target.getText(StandardCharsets.UTF_8.name()).replace("\r\n", "\n")
+ )
+ }
+}
diff --git a/format/yaml/src/test/groovy/org/spongepowered/configurate/yaml/YamlParserComposerTest.groovy b/format/yaml/src/test/groovy/org/spongepowered/configurate/yaml/YamlParserComposerTest.groovy
new file mode 100644
index 000000000..983dd1731
--- /dev/null
+++ b/format/yaml/src/test/groovy/org/spongepowered/configurate/yaml/YamlParserComposerTest.groovy
@@ -0,0 +1,239 @@
+/*
+ * Configurate
+ * Copyright (C) zml and Configurate contributors
+ *
+ * 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 org.spongepowered.configurate.yaml
+
+import static org.assertj.core.api.Assertions.assertThat
+import static org.assertj.core.api.Assertions.assertThatThrownBy
+
+import org.junit.jupiter.api.Disabled
+import org.junit.jupiter.api.Test
+import org.spongepowered.configurate.ConfigurationNode
+import org.spongepowered.configurate.loader.ParsingException
+
+/**
+ * Tests of the basic functionality of our composer implementation.
+ *
+ * Comment-specific testing is handled in {@link CommentTest}
+ */
+class YamlParserComposerTest implements YamlTest {
+
+ @Test
+ void testEmptyDocument() throws IOException {
+ final ConfigurationNode result = parseString("")
+ assertThat(result.empty()).isTrue()
+ assertThat(result.raw()).isNull()
+ }
+
+ @Test
+ void testDuplicateKeysForbidden() throws IOException {
+ assertThatThrownBy { parseString '{duplicated: 1, duplicated: 2}' }
+ .isInstanceOf(ParsingException)
+ .hasMessageContaining("Duplicate key")
+ }
+
+ // Different types of scalars (folded, block, etc)
+
+ @Test
+ void testLoadPlainScalar() {
+ def result = parseString "hello world"
+ assertThat(result.raw())
+ .isEqualTo("hello world")
+
+ assertThat(result.hint(YamlConfigurationLoader.SCALAR_STYLE))
+ .isEqualTo(ScalarStyle.UNQUOTED)
+ }
+
+ @Test
+ void testLoadDoubleQuotedScalar() {
+ def result = parseString '"hello world"'
+ assertThat(result.raw())
+ .isEqualTo("hello world")
+
+ assertThat(result.hint(YamlConfigurationLoader.SCALAR_STYLE))
+ .isEqualTo(ScalarStyle.DOUBLE_QUOTED)
+ }
+
+ @Test
+ void testLoadSingleQuotedScalar() {
+ def result = parseString "'hello world'"
+ assertThat(result.raw())
+ .isEqualTo("hello world")
+
+ assertThat(result.hint(YamlConfigurationLoader.SCALAR_STYLE))
+ .isEqualTo(ScalarStyle.SINGLE_QUOTED)
+ }
+
+ @Test
+ void testLoadFoldedScalar() {
+ def result = parseString("""\
+ test: >
+ hello
+ world\
+ """.stripIndent(true).trim()).node("test")
+
+ assertThat(result.raw())
+ .isEqualTo("hello world")
+
+ assertThat(result.hint(YamlConfigurationLoader.SCALAR_STYLE))
+ .isEqualTo(ScalarStyle.FOLDED)
+ }
+
+ @Test
+ void testLoadBlockScalar() {
+ def result = parseString("""\
+ test: |
+ hello
+ world
+ """.stripIndent(true).trim()).node("test")
+
+ assertThat(result.raw())
+ .isEqualTo("hello\nworld")
+
+ assertThat(result.hint(YamlConfigurationLoader.SCALAR_STYLE))
+ .isEqualTo(ScalarStyle.LITERAL)
+
+ }
+
+ // More complex data structures
+
+ @Test
+ void testLoadMap() {
+ def result = parseString """\
+ hello:
+ world: yup!
+ two: [one, two, three]
+ "yes":
+ aaa: bbb
+ ccc: ddd
+ """.stripIndent(true)
+
+ assertThat(result.node('hello', 'world').raw())
+ .isEqualTo("yup!")
+
+ assertThat(result.node('hello', 'two', 0).raw())
+ .isEqualTo('one')
+
+ assertThat(result.node('hello', 'two').getList(String))
+ .containsExactly('one', 'two', 'three')
+
+ assertThat(result.node('hello', 'yes', 'aaa').raw())
+ .isEqualTo('bbb')
+
+ assertThat(result.node('hello', 'yes', 'ccc').raw())
+ .isEqualTo('ddd')
+ }
+
+ @Test
+ void testLoadSequence() {
+ def result = parseString """\
+ flow: [a, b, c]
+ block:
+ - d
+ - e
+ - f
+ """.stripIndent(true)
+
+ assertThat(result.node('flow').getList(String))
+ .containsExactly('a', 'b', 'c')
+ assertThat(result.node('flow').hint(YamlConfigurationLoader.NODE_STYLE))
+ .isEqualTo(NodeStyle.FLOW)
+
+ assertThat(result.node('block').getList(String))
+ .containsExactly('d', 'e', 'f')
+ assertThat(result.node('block').hint(YamlConfigurationLoader.NODE_STYLE))
+ .isEqualTo(NodeStyle.BLOCK)
+ }
+
+
+ @Test
+ void testLoadAlias() {
+ def result = parseString """\
+ src: &ref [a, b, c]
+ dest: *ref
+ """.stripIndent(true)
+
+ def src = result.node('src')
+ def dest = result.node('dest')
+
+ // Value transferred
+ assertThat(dest.getList(String))
+ .containsExactly('a', 'b', 'c')
+
+ // Anchor information preserved
+ // TODO: this may be different once proper reference nodes are implemented
+ assertThat(src.hint(YamlConfigurationLoader.ANCHOR_ID))
+ .isEqualTo('ref')
+
+ assertThat(dest.hint(YamlConfigurationLoader.ANCHOR_ID))
+ .isNull()
+ }
+
+ @Test
+ void testCommentsOnNullValuePreserved() {
+ def result = parseString """\
+ # the greetings
+ hello:
+ # - abc
+ # - def
+ """
+
+ assertThat(result.node('hello').comment())
+ .isEqualTo("the greetings")
+ }
+
+ // Test that implicit tags are resolved properly
+
+ @Test
+ void testMergeKey() {
+ def result = parseString """\
+ src: &ref
+ old: merged
+ dest:
+ <<: *ref
+ new: added
+ """.stripIndent(true)
+
+ def src = result.node('src')
+ def dest = result.node('dest')
+
+ // Value transferred
+ assertThat(dest.childrenMap().keySet())
+ .containsExactly('old', 'new')
+ }
+
+ @Test
+ void testYIsNotBoolean() {
+ def result = parseString """\
+ asVal: y
+ y: asKey
+ """
+
+ assertThat(result.node('asVal')).with {
+ extracting { it.virtual() }
+ .is(false)
+ extracting { it.raw() }
+ .isEqualTo("y")
+ }
+ assertThat(result.node("y")).with {
+ extracting { it.virtual() }
+ .is(false)
+ extracting { it.raw() }
+ .isEqualTo('asKey')
+ }
+ }
+
+}
diff --git a/format/yaml/src/test/groovy/org/spongepowered/configurate/yaml/YamlTest.groovy b/format/yaml/src/test/groovy/org/spongepowered/configurate/yaml/YamlTest.groovy
new file mode 100644
index 000000000..3f9a81291
--- /dev/null
+++ b/format/yaml/src/test/groovy/org/spongepowered/configurate/yaml/YamlTest.groovy
@@ -0,0 +1,100 @@
+/*
+ * Configurate
+ * Copyright (C) zml and Configurate contributors
+ *
+ * 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 org.spongepowered.configurate.yaml
+
+import static org.junit.jupiter.api.Assertions.assertNotNull
+
+import org.spongepowered.configurate.CommentedConfigurationNode
+import org.yaml.snakeyaml.LoaderOptions
+import org.yaml.snakeyaml.events.CollectionEndEvent
+import org.yaml.snakeyaml.events.CollectionStartEvent
+import org.yaml.snakeyaml.parser.ParserImpl
+import org.yaml.snakeyaml.reader.StreamReader
+import org.yaml.snakeyaml.scanner.ScannerImpl
+
+trait YamlTest {
+
+ CommentedConfigurationNode parseString(final String input) {
+ // Print events
+ def loaderOpts = new LoaderOptions().tap {
+ processComments = true
+ acceptTabs = true
+ }
+ this.dumpEvents(new StreamReader(input), loaderOpts)
+
+ final YamlParserComposer loader = new YamlParserComposer(new StreamReader(input), loaderOpts, Yaml11Tags.REPOSITORY)
+ final CommentedConfigurationNode result = CommentedConfigurationNode.root()
+ loader.singleDocumentStream(result)
+ return result
+ }
+
+ CommentedConfigurationNode parseResource(final URL url) {
+ // Print events
+ def loaderOpts = new LoaderOptions().tap {
+ processComments = true
+ acceptTabs = true
+ }
+ url.openStream().withReader('UTF-8') {reader ->
+ this.dumpEvents(new StreamReader(reader), loaderOpts)
+ }
+
+ assertNotNull(url, "Expected resource is missing")
+ url.openStream().withReader('UTF-8') { reader ->
+ final YamlParserComposer loader = new YamlParserComposer(new StreamReader(reader), loaderOpts, Yaml11Tags.REPOSITORY)
+ final CommentedConfigurationNode result = CommentedConfigurationNode.root()
+ loader.singleDocumentStream(result)
+ return result
+ }
+ }
+
+ private void dumpEvents(StreamReader reader, LoaderOptions loaderOpts) {
+ def scanner = new ScannerImpl(reader, loaderOpts)
+ def parser = new ParserImpl(scanner)
+ int indentLevel = 0
+ while (true) {
+ if (parser.peekEvent() instanceof CollectionEndEvent) {
+ indentLevel--
+ }
+ indentLevel.times {
+ print " "
+ }
+ if (parser.peekEvent() instanceof CollectionStartEvent) {
+ indentLevel++
+ }
+
+ println parser.getEvent()
+ if (!parser.peekEvent()) break
+ }
+ }
+
+ String dump(final CommentedConfigurationNode input) {
+ return dump(input, null)
+ }
+
+ String dump(final CommentedConfigurationNode input, final NodeStyle preferredStyle) {
+ return YamlConfigurationLoader.builder()
+ .nodeStyle(preferredStyle)
+ .indent(2)
+ .commentsEnabled(true)
+ .buildAndSaveString(input)
+ }
+
+ String normalize(final String input) {
+ return input.stripIndent(true)
+ }
+
+}
diff --git a/format/yaml/src/test/groovy/org/spongepowered/configurate/yaml/YamlVisitorTest.groovy b/format/yaml/src/test/groovy/org/spongepowered/configurate/yaml/YamlVisitorTest.groovy
new file mode 100644
index 000000000..82b9f98b8
--- /dev/null
+++ b/format/yaml/src/test/groovy/org/spongepowered/configurate/yaml/YamlVisitorTest.groovy
@@ -0,0 +1,111 @@
+/*
+ * Configurate
+ * Copyright (C) zml and Configurate contributors
+ *
+ * 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 org.spongepowered.configurate.yaml
+
+import static org.junit.jupiter.api.Assertions.assertEquals
+
+import org.junit.jupiter.api.Test
+import org.spongepowered.configurate.CommentedConfigurationNode
+
+/**
+ * Testing YAML emmission.
+ */
+class YamlVisitorTest implements YamlTest {
+
+ @Test
+ void testPlainScalar() {
+ def node = CommentedConfigurationNode.root().set("test")
+
+ assertEquals("test\n", dump(node))
+ }
+
+ @Test
+ void testNumbersAreNonPlainScalar() {
+ def node = CommentedConfigurationNode.root().set("1234")
+
+ assertEquals("\"1234\"\n", dump(node))
+ }
+
+ @Test
+ void testBlockSequence() {
+ final def node = CommentedConfigurationNode.root {
+ appendListNode().set("Hello")
+ appendListNode().set("World")
+ appendListNode().act {
+ it.node("one").set("aaa")
+ it.node("two").set("bbb")
+ }
+ }
+
+ final def expected = normalize("""\
+ - Hello
+ - World
+ - one: aaa
+ two: bbb
+ """)
+ assertEquals(expected, this.dump(node, NodeStyle.BLOCK))
+ }
+
+ @Test
+ void testBlockMapping() {
+ final def node = CommentedConfigurationNode.root {
+ node("meow").set("purr")
+ node("eight").set(1234)
+ node("fun").set(true)
+ }
+
+ final def expected = normalize("""\
+ meow: purr
+ eight: 1234
+ fun: true
+ """)
+ assertEquals(expected, this.dump(node, NodeStyle.BLOCK))
+ }
+
+ @Test
+ void testFlowSequence() {
+ final def node = CommentedConfigurationNode.root {
+ appendListNode().set("Hello")
+ appendListNode().set("World")
+ appendListNode().act {
+ it.node("one").set("aaa")
+ it.node("two").set("bbb")
+ }
+ }
+
+ final def expected = "[Hello, World, {one: aaa, two: bbb}]\n"
+ assertEquals(expected, this.dump(node, NodeStyle.FLOW))
+ }
+
+ @Test
+ void testFlowMapping() {
+ final def node = CommentedConfigurationNode.root {
+ node("meow").set("purr")
+ node("eight").set(1234)
+ node("fun").set(true)
+ }
+
+ final def expected = "{meow: purr, eight: 1234, fun: true}\n"
+ assertEquals(expected, this.dump(node, NodeStyle.FLOW))
+ }
+
+ @Test
+ void testComplex() {
+
+ }
+
+}
diff --git a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java
deleted file mode 100644
index 0c74f0165..000000000
--- a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Configurate
- * Copyright (C) zml and Configurate contributors
- *
- * 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 org.spongepowered.configurate.yaml;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import io.leangen.geantyref.TypeToken;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-import org.spongepowered.configurate.BasicConfigurationNode;
-import org.spongepowered.configurate.CommentedConfigurationNode;
-import org.spongepowered.configurate.ConfigurateException;
-import org.spongepowered.configurate.ConfigurationNode;
-import org.spongepowered.configurate.loader.ConfigurationLoader;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-/**
- * Basic sanity checks for the loader.
- */
-class YamlConfigurationLoaderTest {
-
- @Test
- void testSimpleLoading() throws ConfigurateException {
- final URL url = this.getClass().getResource("/example.yml");
- final ConfigurationLoader loader = YamlConfigurationLoader.builder()
- .url(url).build();
- final ConfigurationNode node = loader.load();
- assertEquals("unicorn", node.node("test", "op-level").raw());
- assertEquals("dragon", node.node("other", "op-level").raw());
- assertEquals("dog park", node.node("other", "location").raw());
-
-
- final List