use std::path::Path;
pub const PAGE_FILE: &str = "index.html";
pub const ROUTED_PAGE_FILE: &str = "404.html";
const INDEX_PATH: &str = "/";
const CONTENT_TYPES: [(&str, &str); 3] =
[("html", "text/html; charset=utf-8"), ("js", "text/javascript; charset=utf-8"), ("wasm", "application/wasm")];
const OTHER_CONTENT: &str = "application/octet-stream";
pub const PACKED_SITE: &[u8; 8] = b"WRPCsite";
pub type SiteFile = (String, Vec<u8>);
pub fn file_at<'a>(files: &'a [SiteFile], path: &str) -> Option<&'a SiteFile> {
let name = if path == INDEX_PATH { PAGE_FILE } else { path.trim_start_matches('/') };
let named = |wanted: &str| files.iter().find(|(file, _)| file == wanted);
named(name).or_else(|| named(ROUTED_PAGE_FILE))
}
pub fn content_type(name: &str) -> &'static str {
let extension = Path::new(name).extension().and_then(|extension| extension.to_str()).unwrap_or_default();
CONTENT_TYPES.iter().find(|(known, _)| *known == extension).map_or(OTHER_CONTENT, |(_, content_type)| content_type)
}
pub fn served(files: &[SiteFile], path: &str) -> Option<(Vec<u8>, &'static str)> {
file_at(files, path).map(|(name, bytes)| (bytes.clone(), content_type(name)))
}
pub fn packed(files: &[SiteFile]) -> Vec<u8> {
let mut bytes = PACKED_SITE.to_vec();
for part in files.iter().flat_map(|(name, content)| [name.as_bytes(), content.as_slice()]) {
bytes.extend_from_slice(&(part.len() as u64).to_le_bytes());
bytes.extend_from_slice(part);
}
bytes
}
pub fn unpacked(bytes: &[u8]) -> Option<Vec<SiteFile>> {
let mut rest = bytes.strip_prefix(PACKED_SITE.as_slice())?;
let mut next = || -> Option<Vec<u8>> {
let (length, after) = rest.split_first_chunk::<8>()?;
let (part, after) = after.split_at_checked(u64::from_le_bytes(*length) as usize)?;
rest = after;
Some(part.to_vec())
};
let mut files = vec![];
while let Some(name) = next() {
files.push((String::from_utf8(name).ok()?, next()?));
}
Some(files)
}