Repository: decorators-squad/eo-yaml
Pull request #448 (2021-05-21 11:26:46)

#447 Mapping is on same line with sequence marker.
PR  for #447

Bug is fixed on yaml lines creation in `RtYamlInput` by moving mapping (key: value) on the next line with an indentation offset of 2 spaces relative to sequence marker.

```yml
- foo: bar
```
becomes:
```yml
-
  foo: bar
```


Patch:
diff --git a/src/main/java/com/amihaiemil/eoyaml/RtYamlInput.java b/src/main/java/com/amihaiemil/eoyaml/RtYamlInput.java
index 8656316..315e082 100644
--- a/src/main/java/com/amihaiemil/eoyaml/RtYamlInput.java
+++ b/src/main/java/com/amihaiemil/eoyaml/RtYamlInput.java
@@ -34,6 +34,7 @@ import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.stream.Stream;
 
 /**
  * Implementation for {@link YamlInput}. "Rt" stands for "Runtime".
@@ -122,6 +123,7 @@ final class RtYamlInput implements YamlInput {
      * Read the input's lines.
      * @return All read YamlLines
      * @throws IOException If something goes wrong while reading the input.
+     * @todo #447:60min Refactor solution for #447 by using lines iterators.
      */
     private AllYamlLines readInput() throws IOException {
         final List<YamlLine> lines = new ArrayList<>();
@@ -133,13 +135,66 @@ final class RtYamlInput implements YamlInput {
             String line;
             int number = 0;
             while ((line = reader.readLine()) != null) {
-                final YamlLine current = new RtYamlLine(line, number);
-                if(!current.toString().trim().isEmpty()) {
-                    lines.add(current);
+
+                if (this.mappingStartsAtDash(line)) {
+
+                    // if line starts with a sequence ("-") and the first
+                    // key:value is unescaped and on the same line with the
+                    // sequence marker, then split the line by keeping the "-"
+                    // on the same indentation and move the key:value on the
+                    // next line with correct indentation relative to "-".
+                    // see bug:
+                    // https://github.com/decorators-squad/eo-yaml/issues/447
+
+                    final String seqIndent = Stream.iterate(" ", s -> s)
+                        .limit(new RtYamlLine(line, number).indentation())
+                        .reduce((acc, space) -> acc + space)
+                        .orElse("");
+                    final YamlLine sequenceLine = new RtYamlLine(
+                        seqIndent + "-",
+                        number
+                    );
+                    lines.add(sequenceLine);
+
+                    // 2 spaces offset
+                    final String offset = "  ";
+                    final String keyValueIndent = seqIndent + offset;
+                    final YamlLine keyValueLine = new RtYamlLine(
+                        keyValueIndent + line.split("-")[1].trim(),
+                        ++number
+                    );
+                    if (!keyValueLine.toString().trim().isEmpty()) {
+                        lines.add(keyValueLine);
+                    }
+                } else {
+                    final YamlLine current = new RtYamlLine(line, number);
+                    if (!current.toString().trim().isEmpty()) {
+                        lines.add(current);
+                    }
                 }
                 number++;
             }
         }
         return new AllYamlLines(lines);
     }
+
+    /**
+     * Is the <i>key:value</i> on the same line as the same sequence marker
+     * <i>-</i> ?.
+     * <br/>
+     * Example:
+     * <br/>
+     * <code>
+     *     - foo: bar
+     * </code>
+     * @param line Line.
+     * @return Boolean.
+     */
+    private boolean mappingStartsAtDash(final String line){
+        //line without indentation.
+        final String trimmed = line.trim();
+        final boolean escapedScalar = trimmed.matches("^[ ]*-[ ]*\".*\"$")
+            || trimmed.matches("^[ ]*-[ ]*'.*'$");
+        return trimmed.matches("^[ ]*-.*:.+$") && !escapedScalar;
+    }
 }


Test patch:
diff --git a/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java b/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java
index bca22ae..cd988c0 100644
--- a/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java
+++ b/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java
@@ -774,6 +774,111 @@ public final class RtYamlInputTest {
         MatcherAssert.assertThat(pretty, Matchers.equalTo(fileContents));
     }
 
+    /**
+     * Corner case when key:value is on the same with sequence marker.
+     * <a href="https://github.com/decorators-squad/eo-yaml/issues/447">#447</a>
+     * @throws IOException If something is wrong.
+     */
+    @Test
+    public void shouldReadSequenceWhenFirstKeyIsOnTheSameLineFirstCase()
+        throws IOException {
+        final String fileName = "src/test/resources/issue_447_bug_mapping"
+            + "_case_1.yml";
+        final YamlMapping root = Yaml.createYamlInput(new File(fileName))
+            .readYamlMapping();
+        final YamlSequence variables = root.yamlSequence("root");
+        MatcherAssert.assertThat(variables, Matchers.iterableWithSize(3));
+    }
+
+    /**
+     * Corner case when key:value is on the same with sequence marker.
+     * <a href="https://github.com/decorators-squad/eo-yaml/issues/447">#447</a>
+     * @throws IOException If something is wrong.
+     */
+    @Test
+    public void shouldReadSequenceWhenFirstKeyIsOnTheSameLineSecondCase()
+        throws IOException {
+        final String fileName = "src/test/resources/issue_447_bug_mapping"
+            + "_case_2.yml";
+        final YamlMapping root = Yaml.createYamlInput(new File(fileName))
+            .readYamlMapping();
+        final YamlSequence variables = root.yamlSequence("root");
+        MatcherAssert.assertThat(variables, Matchers.iterableWithSize(3));
+        MatcherAssert.assertThat(
+            variables.yamlMapping(0).string("key"),
+            Matchers.equalTo("key1")
+        );
+        MatcherAssert.assertThat(
+            variables.yamlMapping(0).string("value"),
+            Matchers.equalTo("value1")
+        );
+        MatcherAssert.assertThat(
+            variables.yamlMapping(1).string("key"),
+            Matchers.equalTo("key2")
+        );
+        MatcherAssert.assertThat(
+            variables.yamlMapping(1).string("value"),
+            Matchers.equalTo("value2")
+        );
+        MatcherAssert.assertThat(
+            variables.yamlMapping(2).string("key"),
+            Matchers.equalTo("key3")
+        );
+        MatcherAssert.assertThat(
+            variables.yamlMapping(2).string("value"),
+            Matchers.equalTo("value3")
+        );
+    }
+
+    /**
+     * Corner case when key:value is on the same with sequence marker.
+     * <a href="https://github.com/decorators-squad/eo-yaml/issues/447">#447</a>
+     * @throws IOException If something is wrong.
+     */
+    @Test
+    public void shouldReadSequenceWhenFirstKeyIsOnTheSameLineThirdCase()
+        throws IOException {
+        final String fileName = "src/test/resources/issue_447_bug_mapping"
+            + "_case_3.yml";
+        final YamlMapping root = Yaml.createYamlInput(new File(fileName))
+            .readYamlMapping();
+        final YamlSequence variables = root.yamlSequence("root");
+        MatcherAssert.assertThat(variables, Matchers.iterableWithSize(3));
+    }
+
+    /**
+     * Corner case when key:value is on the same with sequence marker.
+     * <a href="https://github.com/decorators-squad/eo-yaml/issues/447">#447</a>
+     * and based on
+     * <a href="https://github.com/decorators-squad/eo-yaml/pull/416">PR</a>
+     * @throws IOException If something is wrong.
+     */
+    @Test
+    public void shouldReadSequenceWhenFirstKeyIsOnTheSameLineFourthCase()
+        throws IOException {
+        final String fileName = "src/test/resources/issue_447_bug_mapping"
+            + "_case_4.yml";
+        final YamlMapping root = Yaml.createYamlInput(new File(fileName))
+            .readYamlMapping();
+        MatcherAssert.assertThat(
+            root,
+            Matchers.equalTo(
+                Yaml.createYamlMappingBuilder()
+                    .add("key", "value")
+                    .add("seqMap", Yaml.createYamlSequenceBuilder()
+                        .add(Yaml.createYamlMappingBuilder()
+                            .add("alfa", "b")
+                            .build())
+                        .add(Yaml.createYamlMappingBuilder()
+                            .add("this", "isSkiped")
+                            .build())
+                        .build())
+                    .add("key2", "value")
+                    .build()
+            )
+        );
+    }
+
     /**
      * Read a test resource file's contents.
      * @param fileName File to read.
diff --git a/src/test/resources/issue_447_bug_mapping_case_1.yml b/src/test/resources/issue_447_bug_mapping_case_1.yml
new file mode 100644
index 0000000..866fec8
--- /dev/null
+++ b/src/test/resources/issue_447_bug_mapping_case_1.yml
@@ -0,0 +1,6 @@
+root:
+  - string1
+  - string2
+  - string3
+
+
diff --git a/src/test/resources/issue_447_bug_mapping_case_2.yml b/src/test/resources/issue_447_bug_mapping_case_2.yml
new file mode 100644
index 0000000..d59d12f
--- /dev/null
+++ b/src/test/resources/issue_447_bug_mapping_case_2.yml
@@ -0,0 +1,7 @@
+root:
+  - key: key1
+    value: value1
+  - key: key2
+    value: value2
+  - key: key3
+    value: value3
diff --git a/src/test/resources/issue_447_bug_mapping_case_3.yml b/src/test/resources/issue_447_bug_mapping_case_3.yml
new file mode 100644
index 0000000..b5e592c
--- /dev/null
+++ b/src/test/resources/issue_447_bug_mapping_case_3.yml
@@ -0,0 +1,7 @@
+root:
+  - string1
+  - key: key1
+    value: value1
+  - key: key2
+    value: value2
+
diff --git a/src/test/resources/issue_447_bug_mapping_case_4.yml b/src/test/resources/issue_447_bug_mapping_case_4.yml
new file mode 100644
index 0000000..f2c2088
--- /dev/null
+++ b/src/test/resources/issue_447_bug_mapping_case_4.yml
@@ -0,0 +1,5 @@
+key: value
+seqMap:
+  - alfa: b
+  - this: isSkiped
+key2: value
\ No newline at end of file