///|
fn normalize_path(input : String) -> String raise ScopeError {
  let trimmed = input.trim().to_owned()
  if trimmed is "" {
    raise ScopeError::EmptyPath
  }
  if trimmed.has_prefix("/") ||
    trimmed.has_prefix("\\") ||
    (trimmed.length() >= 2 && trimmed[1] == ':') {
    raise ScopeError::AbsolutePath(input)
  }
  if trimmed.contains("\\") {
    raise ScopeError::AmbiguousSeparator(input)
  }
  let segments : Array[String] = []
  for view in trimmed.split("/") {
    let segment = view.to_owned()
    if segment is "" || segment is "." {
      continue
    }
    if segment is ".." {
      raise ScopeError::ParentTraversal(input)
    }
    segments.push(segment)
  }
  if segments.is_empty() {
    "."
  } else {
    segments.join("/")
  }
}

///|
pub fn path_exact(input : String) -> PathScope raise ScopeError {
  { canonical: normalize_path(input), tree: false, }
}

///|
pub fn path_tree(input : String) -> PathScope raise ScopeError {
  { canonical: normalize_path(input), tree: true, }
}

///|
pub fn PathScope::canonical(self : PathScope) -> String {
  if self.tree {
    if self.canonical is "." {
      "**"
    } else {
      self.canonical + "/**"
    }
  } else {
    self.canonical
  }
}

///|
pub fn PathScope::is_tree(self : PathScope) -> Bool {
  self.tree
}

///|
pub fn path_contains(grant : PathScope, requested : PathScope) -> Bool {
  if !grant.tree {
    return !requested.tree && grant.canonical == requested.canonical
  }
  if grant.canonical is "." {
    return true
  }
  requested.canonical == grant.canonical ||
  requested.canonical.has_prefix(grant.canonical + "/")
}