knave 0.9.3

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::terminal::{ModuleOfSources, Terminal};
use crate::language::Language;

pub struct TestEntries {
    path: PathBuf,
}

impl TestEntries {
    const DIRECTORY_ENTRY: &'static str = "<tests>";
    const MODULE_SEPARATOR: &'static str = "/";

    pub fn new(path: PathBuf) -> Self {
        Self { path }
    }

    pub fn load(&self, terminal: &mut Terminal) -> Result<ModuleOfSources, ExitStatus> {
        if self.path.is_dir() {
            self.load_directory(terminal)
        } else {
            Ok(ModuleOfSources {
                path: self.path.clone(),
                root: Terminal::directory_of(&self.path),
                sources: vec![terminal.load(&self.path)?],
            })
        }
    }

    fn load_directory(&self, terminal: &mut Terminal) -> Result<ModuleOfSources, ExitStatus> {
        let mut modules = Vec::new();
        self.collect_modules(&self.path, &mut modules)
            .map_err(|error| Terminal::report_unreadable(&self.path, &error))?;
        modules.sort();
        let uses: Vec<String> = modules
            .iter()
            .map(|module| format!("(use {module})"))
            .collect();
        let source = terminal.add_named_source(Self::DIRECTORY_ENTRY, &uses.join("\n"));
        Ok(ModuleOfSources {
            path: self.path.join(Self::DIRECTORY_ENTRY),
            root: self.path.clone(),
            sources: vec![source],
        })
    }

    fn collect_modules(&self, directory: &Path, modules: &mut Vec<String>) -> io::Result<()> {
        for entry in fs::read_dir(directory)? {
            let path = entry?.path();
            if path.is_dir() {
                self.collect_modules(&path, modules)?;
            } else if let Some(module) = self.module_path(&path) {
                modules.push(module);
            }
        }
        Ok(())
    }

    fn module_path(&self, file: &Path) -> Option<String> {
        if file.extension()? != Language::EXTENSION {
            return None;
        }
        let relative = file.strip_prefix(&self.path).ok()?.with_extension("");
        let parts: Vec<&str> = relative
            .components()
            .map(|component| component.as_os_str().to_str())
            .collect::<Option<_>>()?;
        Some(parts.join(Self::MODULE_SEPARATOR))
    }
}