knave 0.9.20

A small Lisp for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use std::fs;
use std::io;
use std::path::{Path, PathBuf};

use super::exit_status::ExitStatus;
use super::standard_error::StandardError;
use crate::core::ModulePath;
use crate::language::Language;
use crate::modules::LibraryTable;

#[derive(Clone)]
pub struct ProgramFile {
    path: PathBuf,
    root: PathBuf,
    module: ModulePath,
    libraries: LibraryTable,
}

impl ProgramFile {
    const CURRENT_DIRECTORY: &str = ".";
    const MODULE_SEPARATOR: &str = "/";

    pub fn beside_itself(path: PathBuf) -> Self {
        let root = Self::directory_of(&path);
        Self::within(path, root)
    }

    pub fn within(path: PathBuf, root: PathBuf) -> Self {
        let module = Self::relative_module(&path, &root).unwrap_or_else(|| Self::file_stem(&path));
        Self {
            path,
            root,
            module: ModulePath::new(module),
            libraries: LibraryTable::default(),
        }
    }

    pub fn inside_or_beside(path: PathBuf, root: PathBuf) -> Self {
        match Self::relative_module(&path, &root) {
            Some(module) => Self {
                path,
                root,
                module: ModulePath::new(module),
                libraries: LibraryTable::default(),
            },
            None => Self::beside_itself(path),
        }
    }

    pub fn with_libraries(self, libraries: LibraryTable) -> Self {
        Self { libraries, ..self }
    }

    pub fn libraries(&self) -> &LibraryTable {
        &self.libraries
    }

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

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

    pub fn module_path(&self) -> ModulePath {
        self.module.clone()
    }

    pub fn module_name(relative: &Path) -> Option<String> {
        let without_extension = relative.with_extension("");
        let parts: Vec<&str> = without_extension
            .components()
            .map(|component| component.as_os_str().to_str())
            .collect::<Option<_>>()?;
        Some(parts.join(Self::MODULE_SEPARATOR))
    }

    pub fn is_project_file(path: &Path) -> bool {
        path.file_name()
            .is_some_and(|name| *name == *Language::project_file_name())
    }

    pub fn report_unwritable(path: &Path, error: &io::Error) -> ExitStatus {
        StandardError::write_line(format_args!(
            "error: cannot write `{}`: {error}",
            path.display()
        ));
        ExitStatus::UsageError
    }

    pub fn directory_of(path: &Path) -> PathBuf {
        path.parent().map(Path::to_path_buf).unwrap_or_default()
    }

    fn relative_module(path: &Path, root: &Path) -> Option<String> {
        let directory = Self::canonical(&Self::directory_of(path))?;
        let relative_directory = directory.strip_prefix(Self::canonical(root)?).ok()?;
        Self::module_name(&relative_directory.join(path.file_name()?))
    }

    pub fn canonical(path: &Path) -> Option<PathBuf> {
        fs::canonicalize(Path::new(Self::CURRENT_DIRECTORY).join(path)).ok()
    }

    pub fn file_stem(path: &Path) -> String {
        path.file_stem()
            .map(|stem| stem.to_string_lossy().into_owned())
            .unwrap_or_default()
    }
}