pub fn py_splitlines(s: &str) -> Vec<&str> {
let bytes_indices: Vec<(usize, char)> = s.char_indices().collect();
let mut out = Vec::new();
let mut start = 0usize;
let mut i = 0usize;
while i < bytes_indices.len() {
let (byte_pos, ch) = bytes_indices[i];
let is_boundary = matches!(
ch,
'\n' | '\r' | '\u{0b}' | '\u{0c}' | '\u{1c}' | '\u{1d}' | '\u{1e}' | '\u{85}'
| '\u{2028}' | '\u{2029}'
);
if is_boundary {
let line_end = byte_pos;
let mut next_i = i + 1;
if ch == '\r' && i + 1 < bytes_indices.len() && bytes_indices[i + 1].1 == '\n' {
next_i = i + 2;
}
out.push(&s[start..line_end]);
start = if next_i < bytes_indices.len() {
bytes_indices[next_i].0
} else {
s.len()
};
i = next_i;
continue;
}
i += 1;
}
if start < s.len() {
out.push(&s[start..]);
}
out
}
pub fn py_parent(rel: &str) -> &str {
match rel.rfind('/') {
Some(idx) => &rel[..idx],
None => ".",
}
}
pub fn py_parent_name(rel: &str) -> &str {
let parent = py_parent(rel);
if parent == "." {
return "";
}
match parent.rfind('/') {
Some(idx) => &parent[idx + 1..],
None => parent,
}
}
pub fn py_parts(s: &str) -> Vec<&str> {
if s == "." || s.is_empty() {
return Vec::new();
}
s.split('/').filter(|c| !c.is_empty() && *c != ".").collect()
}
pub fn normpath_join(base: &str, spec: &str) -> String {
let mut parts: Vec<&str> = py_parts(base);
parts.extend(py_parts(spec));
let mut resolved: Vec<&str> = Vec::with_capacity(parts.len());
for p in parts {
if p == ".." {
match resolved.last() {
Some(&last) if last != ".." => {
resolved.pop();
}
_ => resolved.push(".."),
}
} else {
resolved.push(p);
}
}
if resolved.is_empty() {
".".to_string()
} else {
resolved.join("/")
}
}
pub fn path_join_simple(base: &str, comp: &str) -> String {
if base == "." {
comp.to_string()
} else {
format!("{base}/{comp}")
}
}
pub fn path_sort_key(rel: &str) -> Vec<&str> {
rel.split('/').collect()
}
pub fn py_lower(s: &str) -> String {
s.to_lowercase()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn splitlines_basic() {
assert_eq!(py_splitlines("a\nb\n"), vec!["a", "b"]);
assert_eq!(py_splitlines("a\nb"), vec!["a", "b"]);
assert_eq!(py_splitlines(""), Vec::<&str>::new());
assert_eq!(py_splitlines("a\n\nb"), vec!["a", "", "b"]);
assert_eq!(py_splitlines("a\r\nb"), vec!["a", "b"]);
assert_eq!(py_splitlines("a\rb"), vec!["a", "b"]);
}
#[test]
fn parent_basic() {
assert_eq!(py_parent("foo.py"), ".");
assert_eq!(py_parent("a/b/foo.py"), "a/b");
assert_eq!(py_parent("a/foo.py"), "a");
}
#[test]
fn parent_name_basic() {
assert_eq!(py_parent_name("a/b/foo.py"), "b");
assert_eq!(py_parent_name("foo.py"), "");
}
#[test]
fn normpath_join_basic() {
assert_eq!(normpath_join(".", "./x"), "x");
assert_eq!(normpath_join("a/b", "../c"), "a/c");
assert_eq!(normpath_join("a", "../.."), "..");
assert_eq!(normpath_join("a/b", ".."), "a");
assert_eq!(normpath_join("a", ".."), ".");
}
#[test]
fn sort_key_matches_pathlib_order() {
let mut v = vec!["test.py", "test/a.py"];
v.sort_by_key(|p| path_sort_key(p));
assert_eq!(v, vec!["test/a.py", "test.py"]);
}
}