Parsed parseLine(final String s) {
      if (s == null) {
         return null;
      } else {
         Parsed parsed = new Parsed(++row);
         int length = s.length();
         // Trim any space at the end of the line
         while (length > 0 && s.charAt(length - 1) == ' ') length--;
         int state = 0; // 0=INDENT, 1=KEY, 2=COLON, 3=VALUE, 4=TRAILING
         int start = -1;
         for (int i = 0; i < length; i++) {
            column = i + 1;
            int c = s.charAt(i);
            switch (c) {
               case ' ':
                  if (state == 0) {
                     parsed.indent++;
                  } // else ignore: it may be ignorable or part of a key/value
                  break;
               case '\t':
                  if (state == 0) {
                     parsed.indent += INDENT;
                  } // else ignore: it may be ignorable or part of a key/value
                  break;
               case '#':
                  if (state == 3) {
                     parsed.value = s.substring(start, i - 1).trim();
                     return parsed;
                  } else if (state == 1) {
                     throw new ConfigurationReaderException("Invalid comment", new Location(getName(), row, i));
                  } else {
                     return parsed; // the rest of the  line is a comment
                  }
               case ':':
                  if (i + 1 == length || s.charAt(i + 1) == ' ') {
                     if (state == 1) {
                        if (start >= 0) {
                           parseKey(parsed, s.substring(start, i));
                        }
                        state = 2;
                     }
                  }
                  break;
               case '~':
                  if (state == 2 || i + 1 == length) {
                     // the null element
                     parsed.value = null;
                     return parsed;
                  }
               case '\\':
                  if (i + 1 == length) {
                     throw new ConfigurationReaderException("Incomplete escape sequence", new Location(getName(), row, i));
                  } else {
                     if (state == 2) {
                        state = 3;
                        start = i;
                     }
                     i++;
                  }
                  break;
               case '%':
                  if (i == 0) {
                     String[] parts = s.split(" ");
                     if (parts.length == 3 && parts[0].equals("%TAG") && parts[1].startsWith("!") && parts[1].endsWith("!")) {
                        if ("!".equals(parts[1])) {
                           namespaces.put("", parts[2]); // The primary namespace
                        } else {
                           namespaces.put(parts[1].substring(1, parts[1].length() - 1), parts[2]);
                        }
                        return parsed;
                     } else if (parts.length == 2 && parts[0].equals("%YAML")) {
                        return parsed;
                     } else {
                        Log.CONFIG.warn("Unknown directive " + s + " at " + new Location(getName(), row, i));
                     }
                  }
                  break;
               case '-':
                  if (i == 0 && "---".equals(s)) {
                     // It's a separator
                     return parsed;
                  } else if (state == 0) {
                     // It's a list delimiter
                     parsed.list = true;
                     parsed.indent++;
                  }
                  break;
               case '"':
               case '\'':
                  if (state == 0 || state == 2) {
                     int endQuote = s.indexOf(c, i + 1);
                     if (endQuote < 0) {
                        throw new ConfigurationReaderException("Missing closing quote", new Location(getName(), row, i));
                     }
                     String v = s.substring(i + 1, endQuote).trim();
                     if (state == 0) {
                        parseKey(parsed, v);
                        state = 1;
                     } else {
                        parsed.value = v;
                        state = 4;
                     }
                     i = endQuote; // Skip
                     break;
                  }
                  // Fallthrough
               default:
                  if (state == 0) {
                     state = 1;
                     start = i;
                  } else if (state == 2) {
                     state = 3;
                     start = i;
                  }
            }
         }
         if (state == 1) { // Bare string
            if (parsed.list) {
               if (start > 0) {
                  parsed.value = s.substring(start).trim();
               } else {
                  // The value was stored in the name, swap them
                  parsed.value = parsed.name;
                  parsed.name = null;
               }
            } else {
               // It's probably a continuation of the previous line
               parsed.value = unescape(s.substring(start).trim());
            }
         } else if (state == 3) { // we reached the end of the line
            String val = s.substring(start).trim();
            switch (val) {
               // Handle various null values: null | Null | NULL | {}
               case "{}":
               case "null":
               case "Null":
               case "NULL":
                  parsed.value = null;
                  return parsed;
               default:
                  parsed.value = unescape(val);
            }
         }
         return parsed;
      }
   }
@Test
   public void testLine() {
      YamlConfigurationReader yaml = new YamlConfigurationReader(new StringReader(""), new URLConfigurationResourceResolver(null), new Properties(), PropertyReplacer.DEFAULT, NamingStrategy.KEBAB_CASE);
      YamlConfigurationReader.Parsed p = yaml.parseLine("    key: value");
      assertEquals(4, p.indent);
      assertEquals("key", p.name);
      assertEquals("value", p.value);
      p = yaml.parseLine("'key': 'value'");
      assertEquals(0, p.indent);
      assertEquals("key", p.name);
      assertEquals("value", p.value);
      p = yaml.parseLine("    key: value    ");
      assertEquals(4, p.indent);
      assertEquals("key", p.name);
      assertEquals("value", p.value);
      p = yaml.parseLine("  key:");
      assertEquals(2, p.indent);
      assertEquals("key", p.name);
      assertNull(p.value);
      p = yaml.parseLine("  key: # Comment");
      assertEquals(2, p.indent);
      assertEquals("key", p.name);
      assertNull(p.value);
      p = yaml.parseLine("  key: value # Comment");
      assertEquals(2, p.indent);
      assertEquals("key", p.name);
      assertEquals("value", p.value);
      p = yaml.parseLine("  # Comment");
      assertEquals(2, p.indent);
      assertNull(p.name);
      assertNull(p.value);
      p = yaml.parseLine("  - value");
      assertEquals(4, p.indent);
      assertTrue(p.list);
      assertNull(p.name);
      assertEquals("value", p.value);
      p = yaml.parseLine("  - key: value");
      assertEquals(4, p.indent);
      assertTrue(p.list);
      assertEquals("key", p.name);
      assertEquals("value", p.value);

      // Test null values
      assertNull(yaml.parseLine("key: ~").value);
      assertNull(yaml.parseLine("key: {}").value);
      assertNull(yaml.parseLine("key: null").value);
      assertNull(yaml.parseLine("key: Null").value);
      assertNull(yaml.parseLine("key: NULL").value);
      assertEquals("null", yaml.parseLine("key: \"null\"").value);
      assertEquals("nullAndSome", yaml.parseLine("key: \"nullAndSome\"").value);
      assertEquals("Null", yaml.parseLine("key: \"Null\"").value);
      assertEquals("NULL", yaml.parseLine("key: \"NULL\"").value);
      assertEquals("NUll", yaml.parseLine("key: NUll").value);

      Exceptions.expectException(ConfigurationReaderException.class, () -> yaml.parseLine("  key # comment"));
   }