use std::path::Path;
pub const fn delimiter() -> char {
':'
}
#[inline]
pub fn combine(segments: &[&str]) -> String {
segments.join(":")
}
pub fn starts_with(text: impl AsRef<str>, other: impl AsRef<str>) -> bool {
let a = text.as_ref();
let b = other.as_ref();
a.len() >= b.len() && a.chars().zip(b.chars()).all(|(l, r)| l.eq_ignore_ascii_case(&r))
}
pub fn last(path: &str) -> &str {
if let Some(index) = path.rfind(delimiter()) {
&path[(index + 1)..]
} else {
path
}
}
pub fn parent(path: &str) -> &str {
if let Some(index) = path.rfind(delimiter()) {
&path[..index]
} else {
""
}
}
pub fn provider<'a>(path: &'a Path, name: &'a str) -> &'a str {
path.file_name().map(|n| n.to_str().unwrap_or(name)).unwrap_or(name)
}
pub fn next<'a>(path: &'a str, base: Option<&str>) -> Option<&'a str> {
if path.is_empty() {
None
} else if let Some(base) = base {
let len = base.len();
if path.len() > len && path[..len].eq_ignore_ascii_case(base) && path.chars().nth(len) == Some(delimiter()) {
if let Some(index) = path[(len + 1)..].find(delimiter()) {
Some(&path[..(len + 1 + index)])
} else {
Some(path)
}
} else {
None
}
} else if let Some(index) = path.find(delimiter()) {
Some(&path[..index])
} else {
Some(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_case::test_case;
#[test_case(&["parent", ""], "parent:" ; "with 1 segment")]
#[test_case(&["parent", "", ""], "parent::" ; "with 2 segments")]
#[test_case(&["parent", "", "", "key"], "parent:::key" ; "with segments in between")]
fn combine_with_empty_segment_leaves_delimiter(segments: &[&str], expected: &str) {
let path = combine(segments);
assert_eq!(&path, expected);
}
#[test_case("", "" ; "when empty")]
#[test_case(":::", "" ; "when only delimiters")]
#[test_case("a::b:::c", "c" ; "with empty segments in the middle")]
#[test_case("a:::b:", "" ; "when last segment is empty")]
#[test_case("key", "key" ; "with no parent")]
#[test_case(":key", "key" ; "with 1 empty parent")]
#[test_case("::key", "key" ; "with 2 empty parents")]
#[test_case("parent:key", "key" ; "with parent")]
fn last_should_return_expected_segment(path: &str, expected: &str) {
let key = last(path);
assert_eq!(key, expected);
}
#[test_case("", "" ; "when empty")]
#[test_case(":::", "::" ; "when only delimiters")]
#[test_case("a::b:::c", "a::b::" ; "with empty segments in the middle")]
#[test_case("a:::b:", "a:::b" ; "when last segment is empty")]
#[test_case("key", "" ; "with no parent")]
#[test_case(":key", "" ; "with 1 empty parent")]
#[test_case("::key", ":" ; "with 2 empty parents")]
#[test_case("parent:key", "parent" ; "with parent")]
fn parent_should_return_expected_segment(path: &str, expected: &str) {
let key = parent(path);
assert_eq!(key, expected);
}
#[test_case("a", Some("") ; "when empty")]
#[test_case("a", Some("a:b") ; "when path is too short")]
#[test_case("a:b", Some("a:b") ; "when path and base are equal")]
fn next_should_return_none(path: &str, base: Option<&str>) {
let key = next(path, base);
assert_eq!(key, None);
}
#[test_case("a:b", Some("a"), Some("a:b") ; "when base has 1 segment")]
#[test_case("a:b:c", Some("a:b"), Some("a:b:c") ; "when base has 2 segments")]
#[test_case("a:b:c", Some("a"), Some("a:b") ; "when path has 3 segments")]
fn next_should_return_some(path: &str, base: Option<&str>, expected: Option<&str>) {
let key = next(path, base);
assert_eq!(key, expected);
}
}