ini_map.pony

use "collections"

type IniMap is Map[String, Map[String, String]]
  """
  The nested map produced by `IniParse`: outer keys are section names, inner
  keys are key names, inner values are value strings. The empty-string
  section `""` holds any keys that appear before the first section header.
  """

primitive IniParse
  """
  Parse INI-formatted lines into a nested map of sections to key/value
  pairs.

  Duplicate keys in the same section overwrite previous values; the last
  occurrence wins. Repeated `[section]` headers are merged into the existing
  section.
  """
  fun apply(lines: Iterator[String]): IniMap ref^ ? =>
    """
    Parse `lines` into an `IniMap`. Raises an error on the first malformed
    line.
    """
    let map = IniMap

    let f =
      object
        let map: IniMap = map

        fun ref apply(
          section: String,
          key: String,
          value: String)
          : Bool
        =>
          try
            if not map.contains(section) then
              map.insert(section, Map[String, String])
            end
            map(section)?(key) = value
          end
          true

        fun ref add_section(section: String): Bool =>
          if not map.contains(section) then
            map.insert(section, Map[String, String])
          end
          true

        fun ref errors(line: USize, err: IniError): Bool =>
          false
      end

    if not Ini(lines, f) then
      error
    end

    map