#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NexEntry {
pub name: String,
pub is_dir: bool,
}
pub fn parse(body: &str) -> Option<Vec<NexEntry>> {
if !looks_like_directory(body) {
return None;
}
Some(
body.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.map(|line| NexEntry {
name: line.to_string(),
is_dir: line.ends_with('/'),
})
.collect(),
)
}
pub fn looks_like_directory(body: &str) -> bool {
let lines: Vec<&str> = body.lines().filter(|l| !l.trim().is_empty()).collect();
if lines.is_empty() {
return false;
}
lines.iter().all(|line| is_directory_entry_line(line.trim()))
}
fn is_directory_entry_line(line: &str) -> bool {
if line.is_empty() || line.len() > 200 {
return false;
}
!line.contains(char::is_whitespace)
}
pub fn base_url(address: &str) -> String {
if address.ends_with('/') {
return address.to_string();
}
if let Some(idx) = address.rfind('/') {
if let Some(scheme_end) = address.find("://") {
if idx <= scheme_end + 2 {
return format!("{address}/");
}
}
return address[..=idx].to_string();
}
format!("{address}/")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn directory_listing_classifies_dirs_and_files() {
let entries = parse("README.txt\nabout/\nphotos/\ncontact.txt\n").expect("directory");
assert_eq!(
entries,
vec![
NexEntry { name: "README.txt".into(), is_dir: false },
NexEntry { name: "about/".into(), is_dir: true },
NexEntry { name: "photos/".into(), is_dir: true },
NexEntry { name: "contact.txt".into(), is_dir: false },
]
);
}
#[test]
fn whitespace_line_disqualifies_directory() {
assert!(parse("ok\na b\n").is_none());
}
#[test]
fn empty_body_is_not_a_directory() {
assert!(parse("").is_none());
}
#[test]
fn base_url_keeps_trailing_slash() {
assert_eq!(base_url("nex://example.test/path/"), "nex://example.test/path/");
}
#[test]
fn base_url_drops_page_to_parent() {
assert_eq!(base_url("nex://example.test/path/page"), "nex://example.test/path/");
}
#[test]
fn base_url_appends_when_only_authority() {
assert_eq!(base_url("nex://example.test"), "nex://example.test/");
assert_eq!(base_url("nex://example.test/page"), "nex://example.test/");
}
}