///|
/// Returns whether `path` is an absolute Unix-style path.
///
/// Drive letters and UNC paths are intentionally left to a platform adapter.
pub fn path_is_absolute(path : String) -> Bool {
path.has_prefix("/")
}
///|
/// Returns whether `path` is exactly `~` or starts with `~/`.
pub fn path_has_home_prefix(path : String) -> Bool {
path == "~" || path.has_prefix("~/")
}
///|
/// Normalize portable Unix-style path segments without consulting a filesystem.
///
/// Leading `..` segments in a relative path are retained. This deliberately
/// does not resolve symlinks; adapters provide canonical identities for that.
pub fn normalize_path(path : String) -> String {
let absolute = path_is_absolute(path)
let segments : Array[String] = []
for component in path.split("/") {
let part = component.to_owned()
if part == "" || part == "." {
continue
}
if part == ".." {
if !segments.is_empty() && segments[segments.length() - 1] != ".." {
ignore(segments.pop())
} else if !absolute {
segments.push(part)
}
} else {
segments.push(part)
}
}
let body = segments.join("/")
if absolute {
if body == "" {
"/"
} else {
"/\{body}"
}
} else if body == "" {
"."
} else {
body
}
}
///|
/// Return the normalized parent directory of a display path.
pub fn parent_directory(path : String) -> String {
let normalized = normalize_path(path)
if normalized == "/" || normalized == "." {
normalized
} else {
match normalized.rev_find("/") {
Some(index) =>
if index == 0 {
"/"
} else {
normalized[:index].to_owned()
}
None => "."
}
}
}
///|
/// Combine a base directory and a child path without probing the filesystem.
pub fn join_path(base : String, child : String) -> String {
if path_is_absolute(child) {
normalize_path(child)
} else if base == "." || base == "" {
normalize_path(child)
} else if base == "/" {
normalize_path("/\{child}")
} else {
normalize_path("\{base}/\{child}")
}
}
///|
/// Resolve an Include argument against its including file and explicitly
/// injected home directory. It never reads process environment variables.
pub fn resolve_include_path(
including_path : String,
include_path : String,
home : String?,
) -> Result[String, String] {
if path_has_home_prefix(include_path) {
match home {
Some(directory) =>
if include_path == "~" {
Ok(normalize_path(directory))
} else {
Ok(join_path(directory, include_path[2:].to_owned()))
}
None =>
Err("Include path starts with `~/`, but no home directory was supplied")
}
} else if path_is_absolute(include_path) {
Ok(normalize_path(include_path))
} else {
Ok(join_path(parent_directory(including_path), include_path))
}
}