knave 0.14.16

A Lisp programming language 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::diagnostic_output::StandardErrorMessages;
use super::entry_tree::TreeFile;
use super::exit_status::ExitStatus;
use super::located_project::LocatedProject;
use super::program_file::ProgramFile;
use super::skipped_directory::SkippedDirectory;
use super::terminal::Terminal;
use super::tree_bounds::TreeBounds;
use super::unreadable_file::UnreadableFile;
use crate::language::Language;

pub struct SourceWalk {
    root: PathBuf,
    bounds: TreeBounds,
    skipped: SkippedDirectory,
    left_out: Vec<PathBuf>,
}

impl SourceWalk {
    pub fn of(project: &LocatedProject) -> Self {
        let root = Self::walked_root(project);
        let placement = project.placement();
        let left_out = [placement.tasks_directory(), placement.tests_directory()]
            .iter()
            .filter_map(|directory| ProgramFile::canonical(directory))
            .collect();
        Self {
            skipped: SkippedDirectory::for_walk_of(&root),
            bounds: TreeBounds::of(&root),
            root,
            left_out,
        }
    }

    pub fn module_paths(&self, terminal: &mut Terminal) -> Result<Vec<String>, ExitStatus> {
        let files = self.files().map_err(|error| {
            UnreadableFile::new(&self.root, &error).report_to(&StandardErrorMessages)
        })?;
        if let Some(file) = files.iter().find(|file| file.is_unnamable()) {
            return Err(file.report_unnamable());
        }
        TreeFile::refuse_compiler_modules(&files, terminal)?;
        Ok(files.into_iter().map(TreeFile::into_module).collect())
    }

    fn walked_root(project: &LocatedProject) -> PathBuf {
        let source_root = project.source_root();
        if source_root.as_os_str().is_empty() {
            PathBuf::from(Terminal::WORKING_DIRECTORY)
        } else {
            source_root
        }
    }

    fn files(&self) -> io::Result<Vec<TreeFile>> {
        let mut files = Vec::new();
        self.collect(&self.root, &mut files)?;
        files.sort();
        Ok(files)
    }

    fn collect(&self, directory: &Path, files: &mut Vec<TreeFile>) -> io::Result<()> {
        for entry in fs::read_dir(directory)? {
            let path = entry?.path();
            if path.is_dir() {
                self.collect_inside(&path, files)?;
            } else if let Some(module) = self.module_of(&path) {
                files.push(TreeFile::new(module, path));
            }
        }
        Ok(())
    }

    fn collect_inside(&self, directory: &Path, files: &mut Vec<TreeFile>) -> io::Result<()> {
        if self.walks_into(directory) {
            self.collect(directory, files)?;
        }
        Ok(())
    }

    fn walks_into(&self, directory: &Path) -> bool {
        match self.bounds.inside(directory) {
            Some(directory) => {
                !self.skipped.is_skipped(&directory)
                    && !self.left_out.contains(&directory)
                    && !Self::holds_a_project(&directory)
            }
            None => false,
        }
    }

    fn holds_a_project(directory: &Path) -> bool {
        directory.join(Language::project_file_name()).is_file()
    }

    fn module_of(&self, file: &Path) -> Option<String> {
        if file.extension()? != Language::EXTENSION || ProgramFile::is_project_file(file) {
            return None;
        }
        ProgramFile::module_name(file.strip_prefix(&self.root).ok()?)
    }
}