mkwebfont_extract-web 0.4.0

Internal crate for mkwebfont.
Documentation
use crate::consts::CACHE_SIZE;
use anyhow::{bail, Result};
use arcstr::ArcStr;
use mkwebfont_common::hashing::WyHashBuilder;
use moka::future::{Cache, CacheBuilder};
use std::{
    io,
    path::{Path, PathBuf},
    sync::Arc,
};
use tracing::{debug, Instrument};

#[derive(Clone)]
pub struct Webroot(Arc<WebrootData>);
struct WebrootData {
    root: PathBuf,
    cache: Cache<PathBuf, ArcStr, WyHashBuilder>,
}
impl Webroot {
    pub fn new(root: PathBuf) -> Result<Self> {
        Ok(Webroot(Arc::new(WebrootData {
            root: root.canonicalize()?.to_path_buf(),
            cache: CacheBuilder::new(CACHE_SIZE).build_with_hasher(Default::default()),
        })))
    }

    pub fn resolve(&self, rela_root: Option<&Path>, mut path: &str) -> Result<PathBuf> {
        if path.contains('?') {
            path = path.split('?').next().unwrap();
        }

        let resolved_root = if path.starts_with("/") || rela_root.is_none() {
            while path.starts_with("/") {
                path = &path[1..];
            }
            &self.0.root
        } else {
            rela_root.unwrap()
        };
        let mut tmp = resolved_root.to_path_buf();
        tmp.push(path);

        let resolved = tmp.canonicalize()?;
        debug!(
            "Relative path: '{}' + '{path}' = '{}'",
            resolved_root.display(),
            resolved.display()
        );

        if !resolved.starts_with(&self.0.root) {
            bail!("Resolved path '{resolved:?}' is not child of '{:?}'", self.0.root);
        }

        Ok(resolved)
    }

    async fn cache_read(&self, path: &Path) -> Result<ArcStr> {
        Ok(self
            .0
            .cache
            .try_get_with::<_, io::Error>(
                path.to_path_buf(),
                async { Ok(std::fs::read_to_string(path)?.into()) }.in_current_span(),
            )
            .await?)
    }

    pub fn root(&self) -> &Path {
        &self.0.root
    }

    pub async fn load_rela(
        &self,
        rela_root: Option<&Path>,
        path: &str,
    ) -> Result<(ArcStr, RelaWebroot)> {
        let path = self.resolve(rela_root, path)?;
        Ok((self.cache_read(&path).await?, self.rela(&path)?))
    }

    pub async fn load_rela_raw(&self, path: &Path) -> Result<(ArcStr, RelaWebroot)> {
        let path = path.canonicalize()?;
        let rela = self.rela(&path)?;
        Ok((self.cache_read(&path).await?, rela))
    }

    pub fn rela(&self, rela_root: &Path) -> Result<RelaWebroot> {
        let mut new_root = self.0.root.to_path_buf();
        new_root.push(rela_root);
        let path = new_root.canonicalize()?;

        if !path.starts_with(&self.0.root) {
            bail!("Relative path '{path:?}' is not child of '{:?}'", self.0.root);
        }

        Ok(RelaWebroot {
            root: self.clone(),
            parent: path.parent().unwrap().to_path_buf().into(),
            file_name: path.into(),
        })
    }
}

#[derive(Clone)]
pub struct RelaWebroot {
    root: Webroot,
    parent: Arc<Path>,
    file_name: Arc<Path>,
}
impl RelaWebroot {
    pub fn resolve(&self, path: &str) -> Result<PathBuf> {
        self.root.resolve(Some(&self.parent), path)
    }

    pub async fn load_rela(&self, path: &str) -> Result<(ArcStr, RelaWebroot)> {
        self.root.load_rela(Some(&self.parent), path).await
    }

    pub fn root(&self) -> &Webroot {
        &self.root
    }

    pub fn rela_key(&self) -> &Arc<Path> {
        &self.parent
    }

    pub fn file_name(&self) -> &Arc<Path> {
        &self.file_name
    }
}