knave 0.14.14

A Lisp programming language for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use std::fmt::{self, Display};
use std::io;
use std::path::Path;

use super::diagnostic_output::MessageOutput;
use crate::cli::exit_status::ExitStatus;

pub struct UnreadableFile<'a> {
    path: &'a Path,
    error: &'a io::Error,
}

impl<'a> UnreadableFile<'a> {
    const EXIT_STATUS: ExitStatus = ExitStatus::UsageError;

    pub fn new(path: &'a Path, error: &'a io::Error) -> Self {
        Self { path, error }
    }

    pub fn report_to(&self, output: &dyn MessageOutput) -> ExitStatus {
        output.write_error(&self.to_string());
        Self::EXIT_STATUS
    }
}

impl Display for UnreadableFile<'_> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "cannot read `{}`: {}",
            self.path.display(),
            self.error
        )
    }
}

#[cfg(test)]
mod tests {
    use std::cell::RefCell;
    use std::io;
    use std::path::Path;

    use super::UnreadableFile;
    use crate::cli::diagnostic_output::MessageOutput;
    use crate::cli::exit_status::ExitStatus;

    #[derive(Default)]
    struct RecordedErrors {
        errors: RefCell<Vec<String>>,
    }

    impl MessageOutput for RecordedErrors {
        fn write_error(&self, message: &str) {
            self.errors.borrow_mut().push(message.to_string());
        }
    }

    struct UnreadableFixture {
        error: io::Error,
        output: RecordedErrors,
    }

    impl UnreadableFixture {
        const PATH: &str = "notes/missing.knv";
        const CAUSE: &str = "no such file";
        const DESCRIPTION: &str = "cannot read `notes/missing.knv`: no such file";

        fn new() -> Self {
            Self {
                error: io::Error::new(io::ErrorKind::NotFound, Self::CAUSE),
                output: RecordedErrors::default(),
            }
        }

        fn file(&self) -> UnreadableFile<'_> {
            UnreadableFile::new(Path::new(Self::PATH), &self.error)
        }

        fn report(&self) -> ExitStatus {
            self.file().report_to(&self.output)
        }

        fn errors(&self) -> Vec<String> {
            self.output.errors.borrow().clone()
        }
    }

    #[test]
    fn describes_the_path_and_the_cause() {
        let fixture = UnreadableFixture::new();
        assert_eq!(fixture.file().to_string(), UnreadableFixture::DESCRIPTION);
    }

    #[test]
    fn writes_its_description_once_as_an_error() {
        let fixture = UnreadableFixture::new();
        fixture.report();
        assert_eq!(fixture.errors(), [UnreadableFixture::DESCRIPTION]);
    }

    #[test]
    fn reports_a_usage_error() {
        let fixture = UnreadableFixture::new();
        assert_eq!(fixture.report(), ExitStatus::UsageError);
    }
}