knave-expand 0.14.22

The reading, scoping and expansion stages of knave
Documentation
use std::path::PathBuf;

use crate::core::ModulePath;
use crate::language::Language;

#[derive(Clone)]
pub struct LibraryRoot {
    name: String,
    source_root: PathBuf,
    display_root: PathBuf,
    root_module: String,
    visible: Vec<String>,
    lister: Option<String>,
    checkout: Option<PathBuf>,
}

#[derive(Clone, Default)]
pub struct LibraryTable {
    program_visible: Vec<String>,
    libraries: Vec<LibraryRoot>,
}

pub struct LibraryFile {
    pub path: PathBuf,
    pub display: PathBuf,
}

impl LibraryRoot {
    pub fn new(
        name: String,
        source_root: PathBuf,
        display_root: PathBuf,
        root_module: String,
        visible: Vec<String>,
    ) -> Self {
        Self {
            name,
            source_root,
            display_root,
            root_module,
            visible,
            lister: None,
            checkout: None,
        }
    }

    pub fn within_checkout(self, checkout: PathBuf) -> Self {
        Self {
            checkout: Some(checkout),
            ..self
        }
    }

    pub fn listed_by(self, lister: Option<String>) -> Self {
        Self { lister, ..self }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn lister(&self) -> Option<&str> {
        self.lister.as_deref()
    }

    fn file(&self, inner_module: &str) -> LibraryFile {
        let file_name = format!("{inner_module}.{}", Language::EXTENSION);
        LibraryFile {
            path: self.source_root.join(&file_name),
            display: self.display_root.join(&file_name),
        }
    }
}

impl LibraryTable {
    const SEPARATOR: char = '/';

    pub fn new(program_visible: Vec<String>, libraries: Vec<LibraryRoot>) -> Self {
        Self {
            program_visible,
            libraries,
        }
    }

    pub fn resolve(&self, owner: Option<&str>, written: &ModulePath) -> ModulePath {
        match self.named(owner) {
            Some(library) if !self.sees_library(owner, written) => {
                ModulePath::new(format!("{}{}{written}", library.name, Self::SEPARATOR))
            }
            _ => written.clone(),
        }
    }

    pub fn unlisted_library(
        &self,
        owner: Option<&str>,
        written: &ModulePath,
    ) -> Option<&LibraryRoot> {
        if owner.is_some() {
            return None;
        }
        let library = self.owner_of(written)?;
        (!self.sees_library(None, written)).then_some(library)
    }

    pub fn names_library(&self, owner: Option<&str>, written: &ModulePath) -> bool {
        self.sees_library(owner, written)
    }

    fn named(&self, owner: Option<&str>) -> Option<&LibraryRoot> {
        let owner = owner?;
        self.libraries.iter().find(|library| library.name == owner)
    }

    fn sees_library(&self, owner: Option<&str>, written: &ModulePath) -> bool {
        let visible = self
            .named(owner)
            .map_or(&self.program_visible, |library| &library.visible);
        visible.iter().any(|name| name == Self::head(written))
    }

    pub fn file(&self, path: &ModulePath) -> Option<LibraryFile> {
        let library = self.owner_of(path)?;
        let inner = path
            .as_str()
            .strip_prefix(library.name.as_str())?
            .strip_prefix(Self::SEPARATOR);
        Some(library.file(inner.unwrap_or(&library.root_module)))
    }

    pub fn checkout_of(&self, path: &ModulePath) -> Option<&std::path::Path> {
        self.owner_of(path)?.checkout.as_deref()
    }

    pub fn owner_of(&self, path: &ModulePath) -> Option<&LibraryRoot> {
        self.libraries
            .iter()
            .find(|library| library.name == Self::head(path))
    }

    pub fn head(path: &ModulePath) -> &str {
        path.as_str()
            .split(Self::SEPARATOR)
            .next()
            .unwrap_or_default()
    }
}