use crate::rust::Checksum;
use serde::{Deserialize, Serialize, Serializer};
use std::{
collections::BTreeMap,
ffi::OsString,
fmt::{Display, Formatter, Result as FmtResult},
path::{Component, Path, PathBuf},
};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EntryName(OsString);
impl EntryName {
pub fn new<S: Into<OsString>>(input: S) -> Self {
Self(input.into())
}
}
impl Display for EntryName {
fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
for chunk in self.0.as_encoded_bytes().utf8_chunks() {
for character in chunk.valid().chars() {
#[allow(clippy::match_like_matches_macro)]
let escape = match character {
character if character.is_control() => true,
_ => false,
};
match escape {
true => {
let mut buffer = [0; 4];
let string = character.encode_utf8(&mut buffer);
write!(fmt, "\\u{{")?;
for byte in string.bytes() {
write!(fmt, "{byte:02x}")?;
}
write!(fmt, "}}")?;
}
false => write!(fmt, "{character}")?,
}
}
for byte in chunk.invalid().iter() {
write!(fmt, "\\x{{{byte:02x}}}")?;
}
}
Ok(())
}
}
#[test]
fn test_entry_name_display() {
assert_eq!(&EntryName::new("abc".to_string()).to_string(), "abc");
assert_eq!(
&EntryName::new("abc def".to_string()).to_string(),
"abc def"
);
assert_eq!(
&EntryName::new("file.rs".to_string()).to_string(),
"file.rs"
);
}
impl Serialize for EntryName {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
let encoded = self.to_string();
serializer.serialize_str(&encoded)
} else {
serializer.serialize_bytes(self.0.as_encoded_bytes())
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct Node {
pub name: OsString,
pub mode: u32,
pub uid: u64,
pub gid: u64,
pub mtime: u64,
pub kind: Kind,
}
#[derive(Error, Debug)]
pub enum MkdirError {
#[error("non-relative path")]
NonRelativePath,
#[error("parent dir")]
ParentDir,
#[error("encountered file")]
EncounteredFile,
}
#[derive(Error, Debug)]
pub enum InsertError {
#[error("entry exists already")]
Exists,
#[error("parent is not a directory")]
NotDirectory,
}
impl Node {
pub fn mkdir(&mut self, path: &Path) -> Result<&mut Self, MkdirError> {
let mut node = self;
for component in path.components() {
let next = match component {
Component::RootDir => return Err(MkdirError::NonRelativePath),
Component::Prefix(_) => return Err(MkdirError::NonRelativePath),
Component::CurDir => continue,
Component::ParentDir => return Err(MkdirError::ParentDir),
Component::Normal(path) => path,
};
node = match &mut node.kind {
Kind::File(_) => return Err(MkdirError::EncounteredFile),
Kind::Link(_) => return Err(MkdirError::EncounteredFile),
Kind::Directory(children) => children
.entry(next.into())
.or_insert_with(|| Node::dir(next.into())),
};
}
Ok(node)
}
pub fn root() -> Self {
Self::dir("".into())
}
pub fn dir(name: OsString) -> Self {
Node {
name,
mode: 0o644,
uid: 0,
gid: 0,
mtime: 0,
kind: Kind::Directory(BTreeMap::default()),
}
}
pub fn insert(&mut self, node: Node) -> Result<(), InsertError> {
match &mut self.kind {
Kind::Directory(ref mut children) if !children.contains_key(&node.name) => {
children.insert(node.name.clone(), node);
Ok(())
}
Kind::Directory(_) => Err(InsertError::Exists),
_ => Err(InsertError::NotDirectory),
}
}
pub fn objects(&self) -> Box<dyn Iterator<Item = Checksum> + '_> {
match &self.kind {
Kind::Link(_) => Box::new(std::iter::empty()),
Kind::File(checksum) => Box::new(std::iter::once(*checksum)),
Kind::Directory(children) => Box::new(children.values().flat_map(|c| c.objects())),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum Kind {
File(Checksum),
Link(PathBuf),
Directory(BTreeMap<OsString, Node>),
}
impl Default for Kind {
fn default() -> Self {
Kind::Directory(Default::default())
}
}