soop3 0.14.0

the based http fileserver (rust port)
Documentation
// directory listing html generation and sorting helpers

use crate::utils::{
    files::{DirectoryEntry, escape_html, format_file_size, format_timestamp},
    paths::encode_path_segments,
};

pub fn sort_entries(entries: &mut [DirectoryEntry]) {
    entries.sort_by(|a, b| match (a.is_dir, b.is_dir) {
        (true, false) => std::cmp::Ordering::Less,
        (false, true) => std::cmp::Ordering::Greater,
        _ => a.name.cmp(&b.name),
    });
}

pub fn build_listing_html(entries: &[DirectoryEntry], request_path: &str) -> String {
    let mut html = String::new();

    // html document structure
    html.push_str("<!DOCTYPE html>");
    html.push_str("<html><head>");
    html.push_str("<meta charset=\"utf-8\">");
    html.push_str("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
    html.push_str(&format!(
        "<meta name=\"generator\" content=\"soop3 v{}\">",
        env!("CARGO_PKG_VERSION")
    ));
    html.push_str("<link rel=\"icon\" href=\"/__soop_static/icon.svg\">");
    html.push_str(&format!(
        "<title>soop3 | {}</title>",
        escape_html(request_path)
    ));
    html.push_str("<link rel=\"stylesheet\" href=\"/__soop_static/style.css\">");
    html.push_str("</head><body>");

    // content structure
    html.push_str("<div class=\"wrapper\">");
    html.push_str("<main>");
    html.push_str(
        "<a href=\"/\"><img src=\"/__soop_static/icon.svg\" alt=\"logo\" class=\"logo-icon\"></a>",
    );
    html.push_str(&format!(
        "<h1 class=\"index-info\">Index of <code>{}</code></h1>",
        escape_html(request_path)
    ));

    // file listing table
    html.push_str("<table class=\"list\">");
    html.push_str("<tr><th>name</th><th>size</th><th>modified</th></tr>");

    // parent directory link
    if request_path != "/" {
        html.push_str("<tr><td><a href=\"../\">../</a></td><td></td><td></td></tr>");
    }

    // directory entries
    for entry in entries {
        let display_name = if entry.is_dir {
            format!("{}/", entry.name)
        } else {
            entry.name.clone()
        };

        let size_str = if entry.is_dir {
            String::new()
        } else {
            format_file_size(entry.size)
        };

        // use relative links to avoid double-encoding any pre-encoded request paths
        let entry_path = if entry.is_dir {
            format!("{}/", entry.name)
        } else {
            entry.name.clone()
        };
        let encoded_entry_path = encode_path_segments(&entry_path);

        html.push_str(&format!(
            "<tr><td><a href=\"{}\">{}</a></td><td>{}</td><td>{}</td></tr>",
            escape_html(&encoded_entry_path),
            escape_html(&display_name),
            escape_html(&size_str),
            format_timestamp(entry.modified)
        ));
    }

    html.push_str("</table>");
    html.push_str("</main>");
    html.push_str(&format!(
        "<footer><p>Generated by <code>soop3 v{}</code></p></footer>",
        env!("CARGO_PKG_VERSION")
    ));
    html.push_str("</div></body></html>");

    html
}