bat-cli 0.8.10

Blockchain Auditor Toolkit (BAT)
use super::CommandError;
use std::env;

use crate::batbelt::templates::TemplateGenerator;
use crate::batbelt::{BatEnumerator, ShareableData};
use crate::config::{BatAuditorConfig, BatConfig};
use colored::Colorize;
use error_stack::{FutureExt, Report, ResultExt};
use error_stack::{IntoReport, Result};

use crate::batbelt;

use crate::batbelt::git::git_commit::GitCommit;

use crate::batbelt::parser::entrypoint_parser::EntrypointParser;
use crate::batbelt::path::BatFile::GitIgnore;
use crate::batbelt::path::{BatFile, BatFolder};
use crate::batbelt::templates::code_overhaul_template::CodeOverhaulTemplate;
use crate::batbelt::templates::package_json_template::PackageJsonTemplate;
use crate::commands::{BatCommandEnumerator, CommandResult};
use clap::Subcommand;

use crate::batbelt::git::git_action::GitAction;
use crate::commands::sonar_commands::SonarCommand;
use std::path::Path;
use std::process::Command;

#[derive(
    Subcommand, Debug, strum_macros::Display, PartialEq, Clone, strum_macros::EnumIter, Default,
)]
pub enum ProjectCommands {
    #[default]
    New,
    Reload,
}
impl BatEnumerator for ProjectCommands {}

impl BatCommandEnumerator for ProjectCommands {
    fn execute_command(&self) -> CommandResult<()> {
        match self {
            ProjectCommands::New => self.new_bat_project(),
            ProjectCommands::Reload => self.reload_bat_project(),
        }
    }

    fn check_metadata_is_initialized(&self) -> bool {
        false
    }

    fn check_correct_branch(&self) -> bool {
        false
    }
}

impl ProjectCommands {
    fn reload_bat_project(&self) -> CommandResult<()> {
        let bat_auditor_toml_file = BatFile::BatAuditorToml;
        if !bat_auditor_toml_file
            .file_exists()
            .change_context(CommandError)?
        {
            BatAuditorConfig::new_with_prompt().change_context(CommandError)?;
        } else {
            let mut bat_auditor_config =
                BatAuditorConfig::get_config().change_context(CommandError)?;
            bat_auditor_config
                .get_external_bat_metadata()
                .change_context(CommandError)?;
            bat_auditor_config.save().change_context(CommandError)?;
        }
        let auditor_notes_bat_folder = BatFolder::AuditorNotes;
        if !auditor_notes_bat_folder
            .folder_exists()
            .change_context(CommandError)?
        {
            let bat_auditor_config = BatAuditorConfig::get_config().change_context(CommandError)?;
            project_commands_functions::init_auditor_configuration(
                bat_auditor_config.auditor_name,
            )?;
        } else {
            GitAction::CheckoutAuditorBranch
                .execute_action()
                .change_context(CommandError)?;

            project_commands_functions::update_co_to_review()?;
            project_commands_functions::update_package_json()?;
            project_commands_functions::update_git_ignore()?;

            GitCommit::BatReload
                .create_commit(true)
                .change_context(CommandError)?;
        }

        println!("bat project {}", "reloaded!".bright_green());
        Ok(())
    }

    fn new_bat_project(&self) -> Result<(), CommandError> {
        let bat_config = BatConfig::new_with_prompt().change_context(CommandError)?;
        println!("Creating {:#?} project", bat_config);
        TemplateGenerator
            .create_new_project_folders()
            .change_context(CommandError)?;

        let bat_config: BatConfig = BatConfig::get_config().change_context(CommandError)?;

        // Create audit branch from current HEAD (repo already initialized)
        println!("Creating audit branch");
        project_commands_functions::initialize_audit_branch()?;

        PackageJsonTemplate::create_package_json(None).change_context(CommandError)?;

        println!(
            "\n\nRunning Sonar to update {}\n\n",
            "BatMetadata.json!".bright_green()
        );

        SonarCommand::Run {
            skip_source_code: false,
            only_context_accounts: false,
            only_entry_points: false,
            only_traits: false,
            only_function_dependencies: false,
        }
        .execute_command()?;

        // create auditors branches from develop
        for auditor_name in bat_config.auditor_names {
            BatFile::BatAuditorToml
                .create_empty(false)
                .change_context(CommandError)?;
            let bat_auditor_config = BatAuditorConfig {
                auditor_name: auditor_name.clone(),
                miro_oauth_access_token: "".to_string(),
                use_code_editor: false,
                code_editor: Default::default(),
                external_bat_metadata: vec![],
            };
            bat_auditor_config.save().change_context(CommandError)?;

            project_commands_functions::init_auditor_configuration(auditor_name.clone())?;

            BatFile::BatAuditorToml
                .remove_file()
                .change_context(CommandError)?;
        }

        BatAuditorConfig::new_with_prompt().change_context(CommandError)?;

        BatFile::ProgramLib
            .open_in_editor(false, None)
            .change_context(CommandError)?;

        println!("Project {} successfully created", bat_config.project_name);
        Ok(())
    }
}

mod project_commands_functions {
    use super::*;
    use lazy_regex::regex;
    use walkdir::DirEntry;

    pub fn init_auditor_configuration(auditor_name: String) -> CommandResult<()> {
        let bat_config = BatConfig::get_config().change_context(CommandError)?;
        let auditor_project_branch_name = format!("{}-{}", auditor_name, bat_config.project_name);
        let auditor_project_branch_exists =
            batbelt::git::check_if_branch_exists(&auditor_project_branch_name)
                .change_context(CommandError)?;
        if !auditor_project_branch_exists {
            println!("Creating branch {:?}", auditor_project_branch_name);
            // checkout develop to create auditor project branch from there
            Command::new("git")
                .args(["checkout", "develop"])
                .output()
                .unwrap();
            Command::new("git")
                .args(["checkout", "-b", &auditor_project_branch_name])
                .output()
                .unwrap();
        } else {
            println!("Checking out {:?} branch", auditor_project_branch_name);
            // checkout auditor branch
            Command::new("git")
                .args(["checkout", auditor_project_branch_name.as_str()])
                .output()
                .unwrap();
        }
        TemplateGenerator
            .create_folders_for_current_auditor()
            .change_context(CommandError)?;
        initialize_code_overhaul_files()?;
        GitCommit::InitAuditor
            .create_commit(true)
            .change_context(CommandError)?;
        Ok(())
    }

    pub fn update_co_to_review() -> CommandResult<()> {
        println!("Updating code overhaul files");
        // deprecate old entry points
        let co_bat_folder = BatFolder::CodeOverhaulFolderPath;
        let co_dir_file_name = co_bat_folder
            .get_all_bat_files(false, None, None)
            .change_context(CommandError)?;
        // get new entry points
        let entry_points_names = EntrypointParser::get_entrypoint_names_from_program_lib(true)
            .change_context(CommandError)?;
        // get new entry points

        let (old_ep, deprecated_ep): (Vec<BatFile>, Vec<BatFile>) =
            co_dir_file_name.clone().into_iter().partition(|bat_file| {
                entry_points_names.contains(
                    &bat_file
                        .get_file_name()
                        .unwrap()
                        .trim_end_matches(".md")
                        .to_string(),
                )
            });

        let (_, new_ep): (Vec<String>, Vec<String>) =
            entry_points_names.clone().into_iter().partition(|ep_name| {
                co_dir_file_name.clone().into_iter().any(|bat_file| {
                    bat_file.get_file_name().unwrap().trim_end_matches(".md") == ep_name
                })
            });

        let mut updated_eps = vec![];
        // create new ep files
        for ep_name in new_ep {
            println!(
                "Creating code overhaul file for new entry point: {}{}",
                ep_name.bright_blue(),
                ".md".bright_blue()
            );
            let bat_file = BatFile::CodeOverhaulToReview { file_name: ep_name };
            bat_file.create_empty(false).change_context(CommandError)?;
            updated_eps.push(bat_file.get_path(false).change_context(CommandError)?);
        }

        let deprecated_regex = regex!(r#"/code-overhaul/deprecated/"#);

        let filtered_dep = deprecated_ep
            .into_iter()
            .filter(|dep_bat_file| {
                !deprecated_regex.is_match(&dep_bat_file.get_path(false).unwrap())
            })
            .collect::<Vec<_>>();

        // move deprecated to dep folder
        if !filtered_dep.is_empty() {
            let deprecated_co_bat_folder = BatFolder::CodeOverhaulDeprecated;
            if !deprecated_co_bat_folder
                .folder_exists()
                .change_context(CommandError)?
            {
                deprecated_co_bat_folder
                    .create_folder()
                    .change_context(CommandError)?;
            }

            for ep_file in filtered_dep {
                println!(
                    "Moving code overhaul file to deprecated folder: {}",
                    ep_file.get_path(false).unwrap().bright_blue()
                );
                let file_content = ep_file.read_content(false).change_context(CommandError)?;
                let file_name = ep_file.get_file_name().change_context(CommandError)?;
                let deprecated_file = BatFile::CodeOverhaulDeprecated { file_name };
                deprecated_file
                    .write_content(false, &file_content)
                    .change_context(CommandError)?;
                ep_file.remove_file().change_context(CommandError)?;

                updated_eps.push(
                    deprecated_file
                        .get_path(false)
                        .change_context(CommandError)?,
                );
                updated_eps.push(ep_file.get_path(false).change_context(CommandError)?);
            }
        }

        if !updated_eps.is_empty() {
            GitCommit::CodeOverhaulUpdated { updated_eps }
                .create_commit(true)
                .change_context(CommandError)?;
        }
        Ok(())
    }

    pub fn update_package_json() -> CommandResult<()> {
        println!("Updating package.json");
        PackageJsonTemplate::create_package_json(None).change_context(CommandError)
    }

    pub fn update_git_ignore() -> CommandResult<()> {
        println!("Updating .gitignore");
        GitIgnore
            .write_content(true, &TemplateGenerator.get_git_ignore_content())
            .change_context(CommandError)
    }

    pub fn initialize_code_overhaul_files() -> Result<(), CommandError> {
        let entrypoints_names =
            EntrypointParser::get_entrypoint_names_from_program_lib(false).unwrap();

        for entrypoint_name in entrypoints_names {
            create_overhaul_file(entrypoint_name.clone())?;
        }
        Ok(())
    }

    pub fn create_overhaul_file(entrypoint_name: String) -> Result<(), CommandError> {
        let code_overhaul_file_path = BatFile::CodeOverhaulToReview {
            file_name: entrypoint_name.clone(),
        }
        .get_path(false)
        .change_context(CommandError)?;

        if Path::new(&code_overhaul_file_path).is_file() {
            return Err(Report::new(CommandError).attach_printable(format!(
                "code overhaul file already exists for: {entrypoint_name:?}"
            )));
        }

        BatFile::CodeOverhaulToReview {
            file_name: entrypoint_name.clone(),
        }
        .write_content(false, "")
        .change_context(CommandError)?;

        println!(
            "code-overhaul file created: {}{}",
            entrypoint_name.green(),
            ".md".green()
        );

        Ok(())
    }

    pub fn initialize_audit_branch() -> Result<(), CommandError> {
        // We're inside the target repo, create a develop branch for audit work
        println!("Creating develop branch");
        GitAction::CreateBranch {
            branch_name: "develop".to_string(),
        }
        .execute_action()
        .change_context(CommandError)?;

        println!("Committing audit files");
        GitAction::AddAll
            .execute_action()
            .change_context(CommandError)?;
        GitCommit::Init
            .create_commit(true)
            .change_context(CommandError)?;

        Ok(())
    }
}