Repository: decorators-squad/eo-yaml
Pull request #359 (2020-06-16 07:34:46)

#325 Fixed mappings starting at dash line in sequences
PR for #325 

* Bug fixes in indentation validation logic.
* Fixed the reading of mappings which start right at the dash line in a sequence. E.g. first mapping here:

```yaml
- scalar0
- scalar1
- some: mapping
  starting: at-dash
- scalar2
- 
  another: mapping
  onNext: line
```



Patch:
diff --git a/src/main/java/com/amihaiemil/eoyaml/AllYamlLines.java b/src/main/java/com/amihaiemil/eoyaml/AllYamlLines.java
index 9aa2eab..6b8e12c 100644
--- a/src/main/java/com/amihaiemil/eoyaml/AllYamlLines.java
+++ b/src/main/java/com/amihaiemil/eoyaml/AllYamlLines.java
@@ -94,8 +94,8 @@ final class AllYamlLines implements YamlLines {
     }
 
     /**
-     * Try to figure out what YAML node (mapping, sequence or scalar) do these
-     * lines represent.
+     * Try to figure out what YAML node (mapping, sequence or scalar) is found
+     * after the given line.
      * @param prev YamlLine just previous to the node we're trying to find.
      * @return Found YamlNode.
      */
@@ -110,9 +110,9 @@ final class AllYamlLines implements YamlLines {
             line -> line.trimmed().startsWith("%"),
             line -> line.trimmed().startsWith("!!")
         ).iterator().next();
-        if(first.trimmed().startsWith("-")) {
+        if (first.trimmed().startsWith("-")){
             node = new ReadYamlSequence(prev, this);
-        } else if (first.trimmed().contains(":")){
+        } else if(first.trimmed().contains(":")) {
             node = new ReadYamlMapping(prev, this);
         } else if(this.original().size() == 1) {
             node = new ReadPlainScalar(this, first);
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadPlainScalar.java b/src/main/java/com/amihaiemil/eoyaml/ReadPlainScalar.java
index 32cf294..255238e 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadPlainScalar.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadPlainScalar.java
@@ -70,10 +70,10 @@ final class ReadPlainScalar extends BaseScalar {
     public String value() {
         final String value;
         final String trimmed = this.scalar.trimmed();
-        if(trimmed.startsWith("-") && trimmed.length() > 1) {
-            value = trimmed.substring(trimmed.indexOf('-')+1).trim();
-        } else if(trimmed.contains(":") && !trimmed.endsWith(":")) {
+        if(trimmed.contains(":") && !trimmed.endsWith(":")) {
             value = trimmed.substring(trimmed.indexOf(":") + 1).trim();
+        } else if(trimmed.startsWith("-") && trimmed.length() > 1) {
+            value = trimmed.substring(trimmed.indexOf('-')+1).trim();
         } else {
             value = trimmed;
         }
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java b/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
index cedd536..f884e0e 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
@@ -102,29 +102,35 @@ final class ReadYamlMapping extends BaseYamlMapping {
     @Override
     public Set<YamlNode> keys() {
         final Set<YamlNode> keys = new LinkedHashSet<>();
+        YamlLine prev = new YamlLine.NullYamlLine();
         for (final YamlLine line : this.significant) {
             final String trimmed = line.trimmed();
-            if(trimmed.startsWith(":")) {
+            if(trimmed.startsWith(":")
+                || (trimmed.startsWith("-")
+                        && !(prev instanceof YamlLine.NullYamlLine))
+            ) {
                 continue;
             } else if ("?".equals(trimmed)) {
                 keys.add(this.significant.toYamlNode(line));
             } else {
                 if(!trimmed.contains(":")) {
-                    throw new YamlReadingException(
-                        "Expected scalar key on line "
-                        + (line.number() + 1) + "."
-                        + " The line should have the format "
-                        + "'key: value' or 'key:'. "
-                        + "Instead, the line is: "
-                        + "[" + line.trimmed() + "]."
-                    );
+                    continue;
+                }
+                final String key;
+                if(trimmed.startsWith("-")) {
+                    key = trimmed.substring(
+                        1, trimmed.indexOf(":")
+                    ).trim();
+                } else {
+                    key = trimmed.substring(
+                        0, trimmed.indexOf(":")
+                    ).trim();
                 }
-                final String key = trimmed.substring(
-                        0, trimmed.indexOf(":")).trim();
                 if(!key.isEmpty()) {
                     keys.add(new PlainStringScalar(key));
                 }
             }
+            prev = line;
         }
         return keys;
     }
@@ -196,7 +202,8 @@ final class ReadYamlMapping extends BaseYamlMapping {
                     || trimmed.matches("^" + tryKey + "\\:[ ]*\\|$")
                 ) {
                     value = this.significant.toYamlNode(line);
-                } else if(trimmed.startsWith(tryKey + ":")
+                } else if((trimmed.startsWith(tryKey + ":")
+                    || trimmed.startsWith("- " + tryKey + ":"))
                     && trimmed.length() > 1
                 ) {
                     value = new ReadPlainScalar(this.all, line);
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadYamlSequence.java b/src/main/java/com/amihaiemil/eoyaml/ReadYamlSequence.java
index 098ab6b..7761b4e 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadYamlSequence.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadYamlSequence.java
@@ -98,15 +98,31 @@ final class ReadYamlSequence extends BaseYamlSequence {
     @Override
     public Collection<YamlNode> values() {
         final List<YamlNode> kids = new LinkedList<>();
+        final boolean foldedSequence = this.previous.trimmed().matches(
+            "^.*\\|.*\\-$"
+        );
         for(final YamlLine line : this.significant) {
             final String trimmed = line.trimmed();
-            if("-".equals(trimmed)
-                || trimmed.endsWith("|")
-                || trimmed.endsWith(">")
-            ) {
-                kids.add(this.significant.toYamlNode(line));
-            } else {
-                kids.add(new ReadPlainScalar(this.all, line));
+            if(foldedSequence || trimmed.startsWith("-")) {
+                if ("-".equals(trimmed)
+                    || trimmed.endsWith("|")
+                    || trimmed.endsWith(">")
+                ) {
+                    kids.add(this.significant.toYamlNode(line));
+                } else {
+                    if(trimmed.matches("^.*\\-.*\\:.*$")) {
+                        kids.add(
+                            new ReadYamlMapping(
+                                new RtYamlLine(
+                                    "# Mapping at dash line", line.number()-1
+                                ),
+                                this.all
+                            )
+                        );
+                    } else {
+                        kids.add(new ReadPlainScalar(this.all, line));
+                    }
+                }
             }
         }
         return kids;
diff --git a/src/main/java/com/amihaiemil/eoyaml/SameIndentationLevel.java b/src/main/java/com/amihaiemil/eoyaml/SameIndentationLevel.java
index 497093c..0161b24 100644
--- a/src/main/java/com/amihaiemil/eoyaml/SameIndentationLevel.java
+++ b/src/main/java/com/amihaiemil/eoyaml/SameIndentationLevel.java
@@ -76,11 +76,15 @@ final class SameIndentationLevel implements YamlLines {
             final List<YamlLine> sameIndentation = new ArrayList<>();
             final YamlLine first = iterator.next();
             sameIndentation.add(first);
+            int firstIndentation = first.indentation();
+            if(first.trimmed().matches("^[ ]*\\-.*\\:.+$")) {
+                firstIndentation += 2;
+            }
             while (iterator.hasNext()) {
                 YamlLine current = iterator.next();
-                if(current.indentation() == first.indentation()) {
+                if(current.indentation() == firstIndentation) {
                     sameIndentation.add(current);
-                } else if (current.indentation() < first.indentation()) {
+                } else if (current.indentation() < firstIndentation) {
                     break;
                 }
             }
diff --git a/src/main/java/com/amihaiemil/eoyaml/WellIndented.java b/src/main/java/com/amihaiemil/eoyaml/WellIndented.java
index c738ec8..6a5fad3 100644
--- a/src/main/java/com/amihaiemil/eoyaml/WellIndented.java
+++ b/src/main/java/com/amihaiemil/eoyaml/WellIndented.java
@@ -102,9 +102,12 @@ final class WellIndented implements YamlLines {
                 YamlLine line = iterator.next();
                 if(!(previous instanceof YamlLine.NullYamlLine)) {
                     int prevIndent = previous.indentation();
+                    if(previous.trimmed().matches("^[ ]*\\-.*\\:.*$")) {
+                        prevIndent += 2;
+                    }
                     int lineIndent = line.indentation();
                     if(previous.requireNestedIndentation()) {
-                        if(lineIndent != prevIndent+2) {
+                        if(lineIndent != prevIndent + 2) {
                             throw new YamlIndentationException(
                                 "Indentation of line " + (line.number() + 1)
                                 + " is not ok. It should be greater than the one"


Test patch:
diff --git a/src/test/java/com/amihaiemil/eoyaml/ReadYamlSequenceTest.java b/src/test/java/com/amihaiemil/eoyaml/ReadYamlSequenceTest.java
index 0651f54..9ccb89a 100644
--- a/src/test/java/com/amihaiemil/eoyaml/ReadYamlSequenceTest.java
+++ b/src/test/java/com/amihaiemil/eoyaml/ReadYamlSequenceTest.java
@@ -48,6 +48,7 @@ public final class ReadYamlSequenceTest {
 
     /**
      * ReadYamlSequence can return the YamlMapping from a given index.
+     * The YamlMapping starts after the dash line.
      */
     @Test
     public void returnsYamlMappingFromIndex(){
@@ -71,6 +72,71 @@ public final class ReadYamlSequenceTest {
         );
     }
 
+    /**
+     * ReadYamlSequence can return the YamlMapping which starts right
+     * at the dash line. The YamlMapping has a scalar key and another mapping
+     * as value of this key.
+     */
+    @Test
+    public void returnsYamlMappingWithMappingValueStartingAtDash(){
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("- scalar0", 0));
+        lines.add(new RtYamlLine("- scalar1", 1));
+        lines.add(new RtYamlLine("- alfa:", 2));
+        lines.add(new RtYamlLine("    key: value", 3));
+        lines.add(new RtYamlLine("    key2: value2", 4));
+        lines.add(new RtYamlLine("- scalar2", 5));
+        final YamlSequence sequence = new ReadYamlSequence(
+            new AllYamlLines(lines)
+        );
+        System.out.println(sequence);
+        final YamlMapping alfa = sequence.yamlMapping(2);
+        MatcherAssert.assertThat(alfa, Matchers.notNullValue());
+        MatcherAssert.assertThat(alfa, Matchers.instanceOf(YamlMapping.class));
+        MatcherAssert.assertThat(
+            alfa.yamlMapping("alfa").string("key"), Matchers.equalTo("value")
+        );
+        MatcherAssert.assertThat(
+            alfa.yamlMapping("alfa").string("key2"), Matchers.equalTo("value2")
+        );
+        for(final YamlNode value : sequence.values()) {
+            System.out.println(value.type());
+        }
+    }
+
+    /**
+     * ReadYamlSequence can return the YamlMapping which starts right
+     * at the dash line.
+     */
+    @Test
+    public void returnsYamlMappingWithScalarValuesStartingAtDash(){
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("- scalar0", 0));
+        lines.add(new RtYamlLine("- scalar1", 1));
+        lines.add(new RtYamlLine("- alfa: beta", 2));
+        lines.add(new RtYamlLine("  teta: gama", 3));
+        lines.add(new RtYamlLine("  omega: value", 4));
+        lines.add(new RtYamlLine("- scalar2", 5));
+        final YamlSequence sequence = new ReadYamlSequence(
+                new AllYamlLines(lines)
+        );
+        System.out.println(sequence);
+        final YamlMapping dashMap = sequence.yamlMapping(2);
+        MatcherAssert.assertThat(dashMap, Matchers.notNullValue());
+        MatcherAssert.assertThat(
+            dashMap, Matchers.instanceOf(YamlMapping.class)
+        );
+        MatcherAssert.assertThat(
+            dashMap.string("alfa"), Matchers.equalTo("beta")
+        );
+        MatcherAssert.assertThat(
+            dashMap.string("teta"), Matchers.equalTo("gama")
+        );
+        MatcherAssert.assertThat(
+            dashMap.string("omega"), Matchers.equalTo("value")
+        );
+    }
+
     /**
      * ReadYamlSequence can return the YamlSequence from a given index.
      */
diff --git a/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java b/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java
index 4c53223..6a455ef 100644
--- a/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java
+++ b/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java
@@ -605,6 +605,48 @@ public final class RtYamlInputTest {
         );
     }
 
+    /**
+     * A YamlSequence containing mappings which start on the
+     * same line as the dash can be read.
+     * @throws Exception If something goes wrong.
+     */
+    @Test
+    public void readsSequenceWithDashMappings() throws Exception {
+        final YamlMapping map = Yaml.createYamlInput(
+            this.readTestResource("dashMappings.yml")
+        ).readYamlMapping();
+        MatcherAssert.assertThat(map.type(), Matchers.is(Node.MAPPING));
+        final YamlSequence permissions = map.yamlSequence("permissions");
+        MatcherAssert.assertThat(
+            permissions,
+            Matchers.iterableWithSize(4)
+        );
+        MatcherAssert.assertThat(
+            permissions
+                .yamlMapping(0)
+                .yamlSequence("john"),
+            Matchers.iterableWithSize(2)
+        );
+        MatcherAssert.assertThat(
+            permissions
+                .yamlMapping(1)
+                .yamlSequence("jane"),
+            Matchers.iterableWithSize(2)
+        );
+        MatcherAssert.assertThat(
+            permissions
+                .yamlMapping(2)
+                .string("andrew"),
+            Matchers.equalTo("none")
+        );
+        MatcherAssert.assertThat(
+            permissions
+                .yamlMapping(3)
+                .string("andreea"),
+            Matchers.equalTo("none")
+        );
+    }
+
     /**
      * Read a test resource file's contents.
      * @param fileName File to read.
diff --git a/src/test/resources/dashMappings.yml b/src/test/resources/dashMappings.yml
new file mode 100644
index 0000000..d5d5433
--- /dev/null
+++ b/src/test/resources/dashMappings.yml
@@ -0,0 +1,9 @@
+permissions:
+  - john:
+      - download
+      - delete
+  - jane:
+      - deploy
+      - download
+  - andrew: none
+  - andreea: none
\ No newline at end of file