1use std::path::Path;
2
3pub const fn delimiter() -> char {
5 ':'
6}
7
8#[inline]
14pub fn combine(segments: &[&str]) -> String {
15 segments.join(":")
16}
17
18pub fn starts_with(text: impl AsRef<str>, other: impl AsRef<str>) -> bool {
29 let a = text.as_ref();
30 let b = other.as_ref();
31
32 a.len() >= b.len() && a.chars().zip(b.chars()).all(|(l, r)| l.eq_ignore_ascii_case(&r))
33}
34
35pub fn last(path: &str) -> &str {
41 if let Some(index) = path.rfind(delimiter()) {
42 &path[(index + 1)..]
43 } else {
44 path
45 }
46}
47
48pub fn parent(path: &str) -> &str {
54 if let Some(index) = path.rfind(delimiter()) {
55 &path[..index]
56 } else {
57 ""
58 }
59}
60
61pub fn provider<'a>(path: &'a Path, name: &'a str) -> &'a str {
68 path.file_name().map(|n| n.to_str().unwrap_or(name)).unwrap_or(name)
69}
70
71pub fn next<'a>(path: &'a str, base: Option<&str>) -> Option<&'a str> {
78 if path.is_empty() {
79 None
80 } else if let Some(base) = base {
81 let len = base.len();
82
83 if path.len() > len && path[..len].eq_ignore_ascii_case(base) && path.chars().nth(len) == Some(delimiter()) {
84 if let Some(index) = path[(len + 1)..].find(delimiter()) {
85 Some(&path[..(len + 1 + index)])
86 } else {
87 Some(path)
88 }
89 } else {
90 None
91 }
92 } else if let Some(index) = path.find(delimiter()) {
93 Some(&path[..index])
94 } else {
95 Some(path)
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use test_case::test_case;
103
104 #[test_case(&["parent", ""], "parent:" ; "with 1 segment")]
105 #[test_case(&["parent", "", ""], "parent::" ; "with 2 segments")]
106 #[test_case(&["parent", "", "", "key"], "parent:::key" ; "with segments in between")]
107 fn combine_with_empty_segment_leaves_delimiter(segments: &[&str], expected: &str) {
108 let path = combine(segments);
112
113 assert_eq!(&path, expected);
115 }
116
117 #[test_case("", "" ; "when empty")]
118 #[test_case(":::", "" ; "when only delimiters")]
119 #[test_case("a::b:::c", "c" ; "with empty segments in the middle")]
120 #[test_case("a:::b:", "" ; "when last segment is empty")]
121 #[test_case("key", "key" ; "with no parent")]
122 #[test_case(":key", "key" ; "with 1 empty parent")]
123 #[test_case("::key", "key" ; "with 2 empty parents")]
124 #[test_case("parent:key", "key" ; "with parent")]
125 fn last_should_return_expected_segment(path: &str, expected: &str) {
126 let key = last(path);
130
131 assert_eq!(key, expected);
133 }
134
135 #[test_case("", "" ; "when empty")]
136 #[test_case(":::", "::" ; "when only delimiters")]
137 #[test_case("a::b:::c", "a::b::" ; "with empty segments in the middle")]
138 #[test_case("a:::b:", "a:::b" ; "when last segment is empty")]
139 #[test_case("key", "" ; "with no parent")]
140 #[test_case(":key", "" ; "with 1 empty parent")]
141 #[test_case("::key", ":" ; "with 2 empty parents")]
142 #[test_case("parent:key", "parent" ; "with parent")]
143 fn parent_should_return_expected_segment(path: &str, expected: &str) {
144 let key = parent(path);
148
149 assert_eq!(key, expected);
151 }
152
153 #[test_case("a", Some("") ; "when empty")]
154 #[test_case("a", Some("a:b") ; "when path is too short")]
155 #[test_case("a:b", Some("a:b") ; "when path and base are equal")]
156 fn next_should_return_none(path: &str, base: Option<&str>) {
157 let key = next(path, base);
161
162 assert_eq!(key, None);
164 }
165
166 #[test_case("a:b", Some("a"), Some("a:b") ; "when base has 1 segment")]
167 #[test_case("a:b:c", Some("a:b"), Some("a:b:c") ; "when base has 2 segments")]
168 #[test_case("a:b:c", Some("a"), Some("a:b") ; "when path has 3 segments")]
169 fn next_should_return_some(path: &str, base: Option<&str>, expected: Option<&str>) {
170 let key = next(path, base);
174
175 assert_eq!(key, expected);
177 }
178}