///|
pub fn Document::entries(
  self : Document,
  section : String,
  key : String,
) -> Array[Node] {
  self.nodes.filter(n => {
    n.kind == Entry &&
    same_name(n.section, section, self.dialect) &&
    same_name(n.key, key, self.dialect)
  })
}

///|
/// Reject policy returns None for ambiguous duplicates. Inspect diagnostics for cause.
pub fn Document::get(
  self : Document,
  section : String,
  key : String,
) -> String? {
  let entries = self.entries(section, key)
  if entries.is_empty() {
    return None
  }
  match self.dialect.duplicates {
    Reject => if entries.length() == 1 { Some(entries[0].value) } else { None }
    FirstWins => Some(entries[0].value)
    LastWins => Some(entries[entries.length() - 1].value)
  }
}

///|
/// Unique explicit section names in source order, including empty sections.
pub fn Document::sections(self : Document) -> Array[String] {
  let names : Array[String] = []
  for n in self.nodes {
    if n.kind == Section &&
      !names.any(s => same_name(s, n.section, self.dialect)) {
      names.push(n.section)
    }
  }
  names
}

///|
pub fn Document::keys(self : Document, section : String) -> Array[String] {
  let names : Array[String] = []
  for n in self.nodes {
    if n.kind == Entry &&
      same_name(n.section, section, self.dialect) &&
      !names.any(s => same_name(s, n.key, self.dialect)) {
      names.push(n.key)
    }
  }
  names
}