///|
#cfg(platform="windows")
fn separators(path : String) -> String {
path.replace_all(old="\\", new="/")
}
///|
#cfg(not(platform="windows"))
fn separators(path : String) -> String {
path
}
///|
#cfg(not(platform="windows"))
fn absolute(path : String) -> Bool {
path.has_prefix("/")
}
///|
#cfg(platform="windows")
fn absolute(path : String) -> Bool {
(path.length() >= 3 && drive_prefix(path[:2].to_owned()) && path[2] == '/') ||
(
path.has_prefix("//") &&
path.split("/").filter(p => p != "").collect().length() >= 2
)
}
///|
fn drive_prefix(path : String) -> Bool {
path.length() == 2 &&
path[1] == ':' &&
((path[0] >= 'a' && path[0] <= 'z') || (path[0] >= 'A' && path[0] <= 'Z'))
}
///|
fn drive_root(path : String) -> Bool {
path.length() == 3 && path[2] == '/' && drive_prefix(path[:2].to_owned())
}
///|
fn join(base : String, name : String) -> String {
if base.has_suffix("/") {
base + name
} else {
base + "/" + name
}
}
///|
fn parent(path : String) -> String {
let parts = path.split("/").map(p => p.to_owned()).collect()
// Preserve drive and UNC share roots, as well as POSIX '/'.
if parts.length() <= 2 {
if drive_prefix(parts[0]) {
return parts[0] + "/"
}
return "/"
}
if path.has_prefix("//") && parts.length() <= 4 {
return path
}
let result = parts[:parts.length() - 1].to_owned().join("/")
if drive_prefix(result) {
result + "/"
} else {
result
}
}
///|
fn basename(path : String) -> String {
path.split("/").last().unwrap().to_owned()
}
///|
fn normalize(path : String) -> String {
let path = separators(path)
let parts : Array[String] = []
let root_parts = if path.has_prefix("//") {
2
} else if path.length() >= 2 && drive_prefix(path[:2].to_owned()) {
1
} else {
0
}
for p in path.split("/") {
if p == "" || p == "." {
continue
}
if p == ".." {
if parts.length() > root_parts {
ignore(parts.pop())
}
} else {
parts.push(p.to_owned())
}
}
let prefix = if path.has_prefix("//") {
"//"
} else if path.has_prefix("/") {
"/"
} else {
""
}
let result = prefix + parts.join("/")
if drive_prefix(result) {
result + "/"
} else {
result
}
}
///|
fn within(path : String, base : String) -> String? {
if path == base {
return Some("")
}
let prefix = if base.has_suffix("/") { base } else { base + "/" }
if path.has_prefix(prefix) {
Some(path[prefix.length():].to_owned())
} else {
None
}
}
///|
async fn optional_kind(path : String) -> @fs.FileKind? {
try @fs.kind(path, follow_symlink=false) catch {
@os_error.OSError(_) as e if e.is_ENOENT() => None
e => raise e
} noraise {
kind => Some(kind)
}
}
///|
// Resolve the deepest existing ancestor: output need not exist yet. No other
// filesystem errors (especially permission errors) are treated as absence.
async fn output_identity(path : String) -> String {
if optional_kind(path) is Some(_) {
return canonical(path)
}
let up = parent(path)
if up == path {
raise SelectionError::InvalidOutput(path)
}
join(output_identity(up), basename(path))
}
///|
fn utf8_compare(a : String, b : String) -> Int32 {
let a = @utf8.encode(a)
let b = @utf8.encode(b)
for i in 0..