kbvm 0.1.5

An implementation of the XKB specification
Documentation
#[cfg(test)]
mod tests;

use {
    crate::xkb::{
        code::Code,
        interner::{Interned, Interner},
    },
    cfg_if::cfg_if,
    hashbrown::HashMap,
    std::{
        io::{self},
        ops::Range,
        path::{Path, PathBuf},
        sync::Arc,
    },
    thiserror::Error,
};

pub(crate) struct CodeLoader {
    include_paths: Vec<Arc<PathBuf>>,
    cache: HashMap<Key, Vec<Option<CodeFromPath>>>,
    partial_path: Vec<u8>,
    full_path: PathBuf,
}

#[derive(Clone)]
struct CodeFromPath {
    path: Arc<PathBuf>,
    code: Code,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct Key {
    ty: CodeType,
    path: Interned,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) enum CodeType {
    Keycodes,
    Types,
    Compat,
    Symbols,
    Geometry,
    Keymap,
    Rules,
}

#[derive(Debug, Error)]
#[error("could not read file {path}")]
pub(crate) struct CodeLoaderError {
    path: String,
    #[source]
    err: io::Error,
}

impl CodeLoader {
    pub(crate) fn new(include_paths: &[Arc<PathBuf>]) -> Self {
        Self {
            include_paths: include_paths.to_vec(),
            cache: Default::default(),
            partial_path: vec![],
            full_path: Default::default(),
        }
    }

    pub(crate) fn load(
        &mut self,
        interner: &Interner,
        ty: CodeType,
        path: Interned,
    ) -> CodeIter<'_> {
        let key = Key { ty, path };
        let entry = self.cache.entry(key).or_default();
        let path = interner.get(path).clone();
        self.partial_path.clear();
        self.partial_path.extend_from_slice(&path);
        cfg_if! {
            if #[cfg(unix)] {
                use std::os::unix::ffi::OsStrExt;
                use std::ffi::OsStr;
                let path = Some(Path::new(OsStr::from_bytes(&self.partial_path)));
            } else {
                let path = match std::str::from_utf8(&self.partial_path) {
                    Ok(s) => Some(Path::new(s)),
                    _ => None,
                };
            }
        }
        let ty = match path {
            None => CodeIterTy::Absolute { path: None },
            Some(p) => match p.is_absolute() {
                true => CodeIterTy::Absolute { path: Some(p) },
                false => CodeIterTy::Relative {
                    pos: 0..self.include_paths.len(),
                    path: p,
                    paths: &self.include_paths,
                    ty,
                    full_path: &mut self.full_path,
                },
            },
        };
        CodeIter { entry, ty }
    }
}

pub(crate) struct CodeIter<'a> {
    entry: &'a mut Vec<Option<CodeFromPath>>,
    ty: CodeIterTy<'a>,
}

pub(crate) enum CodeIterTy<'a> {
    Absolute {
        path: Option<&'a Path>,
    },
    Relative {
        pos: Range<usize>,
        path: &'a Path,
        paths: &'a [Arc<PathBuf>],
        ty: CodeType,
        full_path: &'a mut PathBuf,
    },
}

impl Iterator for CodeIter<'_> {
    type Item = Result<(Arc<PathBuf>, Code), CodeLoaderError>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            let (pos, path) = match &mut self.ty {
                CodeIterTy::Absolute { path } => (0, Some(path.take()?)),
                CodeIterTy::Relative { pos, .. } => (pos.next()?, None),
            };
            if let Some(entry) = self.entry.get(pos) {
                if let Some(code) = entry {
                    return Some(Ok((code.path.clone(), code.code.clone())));
                }
                continue;
            }
            let path = match path {
                Some(p) => p,
                _ => match &mut self.ty {
                    CodeIterTy::Absolute { path } => path.take()?,
                    CodeIterTy::Relative {
                        path,
                        paths,
                        ty,
                        full_path,
                        ..
                    } => {
                        let sub_dir = match ty {
                            CodeType::Keycodes => "keycodes",
                            CodeType::Types => "types",
                            CodeType::Compat => "compat",
                            CodeType::Symbols => "symbols",
                            CodeType::Geometry => "geometry",
                            CodeType::Keymap => "keymap",
                            CodeType::Rules => "rules",
                        };
                        full_path.clear();
                        full_path.push(&*paths[pos]);
                        full_path.push(sub_dir);
                        full_path.push(path);
                        full_path
                    }
                },
            };
            let code = match std::fs::read(path) {
                Ok(b) => Arc::new(b),
                Err(e) if e.kind() == io::ErrorKind::NotFound => {
                    self.entry.push(None);
                    continue;
                }
                Err(err) => {
                    return Some(Err(CodeLoaderError {
                        path: path.display().to_string(),
                        err,
                    }));
                }
            };
            let code = Code::new(&code);
            let res = CodeFromPath {
                path: Arc::new(path.to_path_buf()),
                code,
            };
            self.entry.push(Some(res.clone()));
            return Some(Ok((res.path, res.code)));
        }
    }
}