use core::fmt::Write;
use std::{
env,
fs::{DirEntry, read_dir},
path::Path,
};
use crate::Result;
macro_rules! path_to_str {
($path:expr) => {
$path.to_str().ok_or_else(|| "Path is invalid Unicode")
};
}
fn size_human(size: u64) -> String {
const UNITS: [&str; 4] = ["bytes", "KiB", "MiB", "GiB"];
let mut i = 0;
let Ok(size) = u32::try_from(size) else {
return format!("{size}");
};
let mut size = f64::from(size);
while i < UNITS.len() {
if size < 1024.0 {
break;
}
size /= 1024.0;
i += 1;
}
let size = (size * 10.0).round() / 10.0;
let mut decimals = 0;
if size.fract() > 0.0 && i == 3 {
decimals = 1;
}
format!("{size:.decimals$} {}", UNITS[i])
}
fn encode_path(path: &Path, show_hidden: bool) -> Result<String> {
let cap = path_to_str!(path)?.len();
let mut encoded_url = String::with_capacity(cap);
for part in path {
let part = path_to_str!(part)?;
encoded_url += "/";
encoded_url += &url::encode(part)?;
}
if cap == 0 {
encoded_url.push('/');
}
if !show_hidden {
encoded_url += "?hidden=false";
}
Ok(encoded_url)
}
pub fn index_of(filename: &str, show_hidden: bool) -> Result<String> {
let cwd_path = env::current_dir()?;
let cwd = path_to_str!(&cwd_path)?;
let mut html = String::from(
"<html><head><meta charset=\"UTF-8\" />\
<style>body{text-align:left;}\
td{padding-right:1em;}\
td:first-child{padding-right:0.2em;}</style></head><body>",
);
let title = Path::new(filename).strip_prefix(cwd)?;
html.write_fmt(format_args!("<h1>Index of / {}</h1>", path_to_str!(title)?))?;
let mut files = Vec::new();
for f in read_dir(filename)? {
files.push(f?);
}
files.sort_by_key(DirEntry::path);
html.push_str("<table><tr><th>Name</th><th>Size</th></tr>");
if let Some(parent) = Path::new(filename).parent() {
if parent.starts_with(cwd) {
let url = parent.strip_prefix(cwd)?;
let url = encode_path(url, show_hidden)?;
html.write_fmt(format_args!(
"<tr><td>←</td><td><a href=\"{url}\">..</a></td></tr>"
))?;
}
}
for file in files {
let path = file.path();
let file = path.metadata()?;
let text = path.strip_prefix(filename)?;
let text = path_to_str!(text)?.to_owned();
if !show_hidden && text.starts_with('.') {
continue;
}
let icon = if file.is_dir() {
"🖿"
} else {
"🗈"
};
let url = path.strip_prefix(cwd)?;
let encoded_path = encode_path(url, show_hidden)?;
html.write_fmt(format_args!(
"<tr><td>{icon}</td><td><a href=\"{encoded_path}\">{text}</a></td>"
))?;
html.write_fmt(format_args!("<td>{}</td>", size_human(file.len())))?;
html.write_str("</tr>")?;
}
html.push_str("</table></body></html>");
Ok(html)
}