libwaj 0.4.1

The library to handle waj file, the pack format for web site.
Documentation
use crate::create::{EntryKind, EntryStoreCreator, EntryTrait, Void};
use crate::error::CreatorError;
use core::option::Option::None;
use jbk::creator::{CompHint, ContentAdder, InputReader};
use mime_guess::mime;
use std::borrow::Cow;
use std::fs;
use std::io::{Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};

pub enum FsEntryKind {
    File(jbk::ContentAddress, mime::Mime),
    Link,
    Other,
}

pub struct FsEntry {
    pub kind: FsEntryKind,
    pub path: PathBuf,
    pub name: String,
}

impl FsEntry {
    pub fn new_from_walk_entry(
        dir_entry: walkdir::DirEntry,
        name: String,
        adder: &mut impl ContentAdder,
    ) -> Result<Box<Self>, CreatorError> {
        let fs_path = dir_entry.path().to_path_buf();
        let attr = dir_entry.metadata().unwrap();
        let kind = if attr.is_file() {
            let mut reader = jbk::creator::InputFile::open(&fs_path)?;
            let mime_type = match mime_guess::from_path(&fs_path).first() {
                Some(m) => m,
                None => {
                    let mut buf = [0u8; 100];
                    let size = std::cmp::min(100, reader.size().into_u64() as usize);
                    reader.read_exact(&mut buf[..size])?;
                    (|| {
                        for window in buf[..size].windows(4) {
                            if window == b"html" {
                                return mime::TEXT_HTML;
                            }
                        }
                        mime::APPLICATION_OCTET_STREAM
                    })()
                }
            };
            reader.seek(SeekFrom::Start(0))?;
            let content_address = adder.add_content(Box::new(reader), CompHint::Detect)?;
            FsEntryKind::File(content_address, mime_type)
        } else if attr.is_symlink() {
            FsEntryKind::Link
        } else {
            FsEntryKind::Other
        };
        Ok(Box::new(Self {
            kind,
            path: fs_path,
            name,
        }))
    }
}

impl EntryTrait for FsEntry {
    fn kind(&self) -> Result<Option<EntryKind>, CreatorError> {
        Ok(match self.kind {
            FsEntryKind::File(content_address, ref mime) => {
                Some(EntryKind::Content(content_address, mime.clone()))
            }
            FsEntryKind::Link => {
                let path = &self.path;
                let target = fs::read_link(path)?;
                let abs_target = path.parent().unwrap().join(&target);
                if abs_target.is_dir() {
                    None
                } else {
                    Some(EntryKind::Redirect(
                        target
                            .to_str()
                            .unwrap_or_else(|| panic!("{path:?} must be a utf8"))
                            .to_owned(),
                    ))
                }
            }
            FsEntryKind::Other => None,
        })
    }
    fn name(&self) -> Cow<'_, str> {
        Cow::Borrowed(&self.name)
    }
}

pub trait Namer {
    fn rename(&self, path: &Path) -> String;
}

pub struct StripPrefix {
    prefix: PathBuf,
}

impl StripPrefix {
    pub fn new(prefix: PathBuf) -> Self {
        Self { prefix }
    }
}

impl Namer for StripPrefix {
    fn rename(&self, path: &Path) -> String {
        path.strip_prefix(&self.prefix)
            .unwrap()
            .to_str()
            .unwrap_or_else(|| panic!("{path:?} must be a utf8"))
            .to_owned()
    }
}

pub struct FsAdder<'a> {
    creator: &'a mut EntryStoreCreator,
    namer: &'a dyn Namer,
}

impl<'a> FsAdder<'a> {
    pub fn new(creator: &'a mut EntryStoreCreator, namer: &'a dyn Namer) -> Self {
        Self { creator, namer }
    }

    pub fn add_from_path<P>(&mut self, path: P, adder: &mut impl ContentAdder) -> Void
    where
        P: AsRef<std::path::Path>,
    {
        self.add_from_path_with_filter(path, |_e| true, adder)
    }

    pub fn add_from_path_with_filter<P, F>(
        &mut self,
        path: P,
        filter: F,
        adder: &mut impl ContentAdder,
    ) -> Void
    where
        P: AsRef<std::path::Path>,
        F: FnMut(&walkdir::DirEntry) -> bool,
    {
        let walker = walkdir::WalkDir::new(path);
        let walker = walker.into_iter();
        for entry in walker.filter_entry(filter) {
            let entry = entry.unwrap();
            let entry_path = entry.path();
            let waj_path = self.namer.rename(entry_path);
            if waj_path.is_empty() {
                continue;
            }
            let entry = FsEntry::new_from_walk_entry(entry, waj_path, adder)?;
            self.creator.add_entry(entry.as_ref())?;
        }
        Ok(())
    }
}