#![forbid(unsafe_code)]
#![forbid(missing_docs)]
use std::collections::BTreeMap;
use std::fmt::{self, Display};
use std::fs::read_to_string;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct Archive {
files: BTreeMap<String, String>,
}
impl Archive {
pub fn load(file: &Path) -> Result<Archive, FileError> {
let data = read_to_string(file).map_err(|e| FileError::Io(file.into(), e))?;
Archive::parse(&data).map_err(|e| FileError::Data(file.into(), e))
}
pub fn parse(data: &str) -> Result<Archive, Error> {
let mut files = BTreeMap::new();
let boundary = format!("\n{}", find_boundary(data).ok_or(Error::NoBoundary)?);
for item in data[boundary.len() - 1..].split(&boundary) {
if item.is_empty() || item.starts_with('\n') {
} else if let Some(item) = item.strip_prefix(' ') {
if let Some((name, body)) = item.split_once('\n') {
files.insert(name.into(), body.into());
} else {
files.insert(item.into(), String::new());
}
} else {
return Err(Error::InvalidItem(item.into()));
}
}
Ok(Archive { files })
}
pub fn names(&self) -> Vec<&str> {
self.files.keys().map(AsRef::as_ref).collect()
}
pub fn get(&self, name: &str) -> Option<&str> {
self.files.get(name).map(AsRef::as_ref)
}
pub fn entries(&self) -> impl Iterator<Item = (&str, &str)> {
self.files.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))
}
}
fn find_boundary(data: &str) -> Option<&str> {
for (i, b) in data.bytes().enumerate() {
match (i, b) {
(0, b'<') | (_, b'=') => (),
(i, b'>') => return Some(&data[0..=i]),
_ => return None,
}
}
None
}
#[derive(Debug)]
pub enum FileError {
Data(PathBuf, Error),
Io(PathBuf, std::io::Error),
}
impl std::error::Error for FileError {}
impl Display for FileError {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
match self {
FileError::Data(path, e) => {
write!(out, "Failed to parse {path:?}: {e}")
}
FileError::Io(path, e) => {
write!(out, "Failed to read {path:?}: {e}")
}
}
}
}
#[derive(Debug)]
pub enum Error {
NoBoundary,
InvalidItem(String),
}
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::NoBoundary => {
write!(out, "No archive boundary found")
}
Error::InvalidItem(item) => {
write!(out, "Invalid item: {item:?}")
}
}
}
}