bat-cli 0.6.6

Blockchain Auditor Toolkit (BAT)
use figment::{
    providers::{Format, Toml},
    Figment,
};
use serde::{Deserialize, Serialize};

use std::{error::Error, fmt, str};

use crate::batbelt::bat_dialoguer::BatDialoguer;
use crate::batbelt::command_line::CodeEditor;
use crate::batbelt::path::BatFile;
use crate::batbelt::BatEnumerator;

use colored::Colorize;
use error_stack::{FutureExt, IntoReport, Result, ResultExt};
use figment::error::Kind;

#[derive(Debug)]
pub struct BatConfigError;

impl fmt::Display for BatConfigError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("BatConfig error")
    }
}

impl Error for BatConfigError {}

pub type BatConfigResult<T> = Result<T, BatConfigError>;

#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialOrd, PartialEq)]
pub struct BatAuditorConfig {
    pub auditor_name: String,
    pub miro_oauth_access_token: String,
    pub use_code_editor: bool,
    pub code_editor: CodeEditor,
}

impl BatAuditorConfig {
    pub fn new_with_prompt() -> BatConfigResult<Self> {
        let mut bat_auditor_config = BatAuditorConfig {
            auditor_name: "".to_string(),
            miro_oauth_access_token: "".to_string(),
            use_code_editor: false,
            code_editor: Default::default(),
        };
        bat_auditor_config.prompt_auditor_name()?;
        bat_auditor_config.prompt_miro_integration()?;
        bat_auditor_config.prompt_code_editor_integration()?;
        bat_auditor_config.save()?;
        Ok(bat_auditor_config)
    }

    fn prompt_auditor_name(&mut self) -> BatConfigResult<()> {
        let bat_config = BatConfig::get_config()?;
        let auditor_names = bat_config.auditor_names;
        let prompt_text = "Select your name:".to_string();
        let selection = BatDialoguer::select(prompt_text, auditor_names.clone(), None)
            .change_context(BatConfigError)?;
        let auditor_name = auditor_names.get(selection).unwrap().clone();
        self.auditor_name = auditor_name;
        Ok(())
    }

    fn prompt_miro_integration(&mut self) -> BatConfigResult<()> {
        let prompt_text = "Do you want to use the Miro integration?";
        let include_miro = BatDialoguer::select_yes_or_no(prompt_text.to_string())
            .change_context(BatConfigError)?;
        let moat = if include_miro {
            let prompt_text = "Miro OAuth access token";
            BatDialoguer::input(prompt_text.to_string()).change_context(BatConfigError)?
        } else {
            "".to_string()
        };
        self.miro_oauth_access_token = moat;
        Ok(())
    }

    fn prompt_code_editor_integration(&mut self) -> BatConfigResult<()> {
        let prompt_text = format!(
            "Select a code editor, choose {} to disable:",
            CodeEditor::None.get_colored_name(false)
        );
        let editor_colorized_vec = CodeEditor::get_colorized_type_vec(false);
        let editor_integration = BatDialoguer::select(prompt_text, editor_colorized_vec, None)
            .change_context(BatConfigError)?;
        self.code_editor = CodeEditor::from_index(editor_integration);
        self.use_code_editor = self.code_editor != CodeEditor::None;
        Ok(())
    }

    pub fn get_config() -> Result<Self, BatConfigError> {
        let path = BatFile::BatAuditorToml
            .get_path(true)
            .change_context(BatConfigError)?;
        let bat_config_result = Figment::new()
            .merge(Toml::file(path))
            .extract()
            .into_report()
            .change_context(BatConfigError)
            .attach_printable("Error parsing BatAuditor.toml");
        return match bat_config_result {
            Ok(bat_auditor_config) => Ok(bat_auditor_config),
            Err(error) => {
                log::error!("Error parsing BatAuditor \n {}", error);
                let frames_errors = error
                    .frames()
                    .filter_map(|frame| {
                        let downcast = frame.downcast_ref::<figment::Error>();
                        if downcast.is_some() {
                            Some(downcast.unwrap())
                        } else {
                            None
                        }
                    })
                    .collect::<Vec<_>>();
                for frame_error in frames_errors {
                    match frame_error.clone().kind {
                        Kind::MissingField(missing_field) => {
                            println!("{} field missing on BatAuditor.toml", missing_field.red());
                        }
                        _ => {}
                    }
                }
                println!("\nCreating {} again\n", "BatAuditor.toml".bright_green());
                Self::new_with_prompt()?;
                let new_config = Self::get_config()?;
                return Ok(new_config);
            }
        };
    }

    pub fn save(&self) -> Result<(), BatConfigError> {
        let path = BatFile::BatAuditorToml
            .get_path(false)
            .change_context(BatConfigError)?;
        confy::store_path(path, self)
            .into_report()
            .change_context(BatConfigError)
    }
}

#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct BatConfig {
    pub initialized: bool,
    pub project_name: String,
    pub client_name: String,
    pub commit_hash_url: String,
    pub starting_date: String,
    pub miro_board_url: String,
    pub auditor_names: Vec<String>,
    pub program_lib_path: String,
    pub program_name: String,
    pub project_repository_url: String,
}

impl BatConfig {
    pub fn get_config() -> Result<Self, BatConfigError> {
        let path = BatFile::BatToml
            .get_path(true)
            .change_context(BatConfigError)?;
        let bat_config: BatConfig = Figment::new()
            .merge(Toml::file(path))
            .extract()
            .into_report()
            .change_context(BatConfigError)
            .attach_printable("Error parsing Bat.toml")?;
        Ok(bat_config)
    }

    pub fn save(&self) -> Result<(), BatConfigError> {
        let path = BatFile::BatToml
            .get_path(false)
            .change_context(BatConfigError)?;
        confy::store_path(path, self)
            .into_report()
            .change_context(BatConfigError)
    }
}