lazin 1.0.4

Primarily a dotfiles manager
use std::{fmt::Display, path::Path};

use codespan_reporting::{
    diagnostic::{Diagnostic, Label},
    files::SimpleFile,
    term::{self, termcolor::Buffer},
};

use crate::config::{self, Name};

#[derive(Debug)]
pub struct TomlError {
    pub filename: String,
    pub source: String,
    pub error: toml::de::Error,
}

impl Display for TomlError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let span = self.error.span().expect("a valid span");
        let file = SimpleFile::new(self.filename.as_str(), self.source.as_str());

        let diagnostic = Diagnostic::error()
            .with_message(self.error.message())
            .with_label(Label::primary((), span));

        let config = codespan_reporting::term::Config::default();
        let mut buffer = Buffer::ansi();

        term::emit_to_write_style(&mut buffer, &config, &file, &diagnostic)
            .expect("a valid error emit");

        let rendered = String::from_utf8(buffer.into_inner()).map_err(|_| std::fmt::Error)?;
        write!(f, "{}", rendered)
    }
}

#[derive(Debug)]
pub enum LazinError {
    Toml(Box<TomlError>),
    Io(std::io::Error),
    DirectoryDoesNotExist(String),
    Custom(&'static str),
    InvalidModuleSources,
    ModuleNotFound(Name, Name),
    StripPrefix(std::path::StripPrefixError),
    GpgWrapper(lazin_gpg_wrapper::Error),
    DuplicateKeys(config::DuplicateKeysError),
}

impl LazinError {
    pub fn directory_does_not_exist(path: &Path) -> Self {
        Self::DirectoryDoesNotExist(format!("{}", path.display()))
    }
}

impl Display for LazinError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LazinError::Toml(error) => write!(f, "Toml error: {}", error),
            LazinError::Io(error) => write!(f, "Io error: {}", error),
            LazinError::Custom(c) => write!(f, "{}", c),
            LazinError::DirectoryDoesNotExist(p) => write!(f, "Directory does not exists: '{}'", p),
            LazinError::InvalidModuleSources => write!(f, "Invalid module sources"),
            LazinError::ModuleNotFound(w, m) => {
                write!(
                    f,
                    "Workspace '{}' contains an unconfigured module '{}'",
                    w.as_ref(),
                    m.as_ref(),
                )
            }
            LazinError::StripPrefix(strip_prefix_error) => {
                write!(f, "Failed to strip prefix: {}", strip_prefix_error)
            }
            LazinError::GpgWrapper(error) => error.fmt(f),
            LazinError::DuplicateKeys(error) => {
                let messages: Vec<String> = [
                    (!error.workspaces.is_empty()).then(|| format!(
                        "Found duplicate workspace names, please make sure all workspaces names are unique. Duplicate names: {}",
                        error.workspaces.join(", ")
                    )),
                    (!error.modules.is_empty()).then(|| format!(
                        "Found duplicate module names, please make sure all module names are unique. Duplicate names: {}",
                        error.modules.join(", ")
                    )),
                ]
                    .into_iter()
                    .flatten()
                    .collect();

                write!(f, "{}", messages.join("\n"))
            }
        }
    }
}

impl std::error::Error for LazinError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            LazinError::Toml(error) => error.error.source(),
            LazinError::Io(error) => error.source(),
            LazinError::Custom(_) => None,
            LazinError::DirectoryDoesNotExist(_) => None,
            LazinError::InvalidModuleSources => None,
            LazinError::ModuleNotFound(_, _) => None,
            LazinError::StripPrefix(strip_prefix_error) => strip_prefix_error.source(),
            LazinError::GpgWrapper(error) => error.source(),
            LazinError::DuplicateKeys(_) => None,
        }
    }
}

impl From<TomlError> for LazinError {
    fn from(value: TomlError) -> Self {
        Self::Toml(Box::new(value))
    }
}

impl From<std::io::Error> for LazinError {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}

impl From<std::path::StripPrefixError> for LazinError {
    fn from(value: std::path::StripPrefixError) -> Self {
        Self::StripPrefix(value)
    }
}

impl From<lazin_gpg_wrapper::Error> for LazinError {
    fn from(value: lazin_gpg_wrapper::Error) -> Self {
        Self::GpgWrapper(value)
    }
}

impl From<config::DuplicateKeysError> for LazinError {
    fn from(value: config::DuplicateKeysError) -> Self {
        Self::DuplicateKeys(value)
    }
}