gitbox 2.1.3

Git toolbox to simplify adoption of conventional commits and semantic version, among other things.
Documentation
use clap::{builder::NonEmptyStringValueParser, Args};

use crate::{
    application::{
        controller::{commit::CommitController, exit_code::ControllerExitCode},
        manager::{
            gitinfo_ingress_manager::GitInfoIngressManager,
            message_egress_manager::MessageEgressManager,
        },
        options::commit::CommitOptions,
    },
    infrastructure::{
        helper::commit_prompt_helper::CommitPromptHelper,
        interface::{
            git_cli::GitCli, gitextra_manager_impl::GitExtraManagerImpl,
            message_egress_manager_impl::MessageEgressManagerImpl,
        },
        subcommand::Subcommand,
    },
    usecase::type_aliases::AnyError,
};

#[derive(Args, Debug)]
#[command(about = "Create a commit with a conventional message")]
pub struct CommitSubCommand {
    #[arg(short = 't', long = "type", help = "Set the type of the commit", value_parser = NonEmptyStringValueParser::new())]
    commit_type: Option<String>,

    #[arg(short = 'S', long, help = "Set the scope of the commit")]
    scope: Option<String>,

    #[arg(
        long = "breaking",
        help = "Flag the commit as a breaking change",
        overrides_with = "is_not_breaking"
    )]
    is_breaking: bool,
    #[arg(
        long = "no-breaking",
        help = "Flag the commit as not a breaking change",
        overrides_with = "is_breaking"
    )]
    is_not_breaking: bool,

    #[arg(short = 's', long, help = "Set the summary of the commit", value_parser = NonEmptyStringValueParser::new())]
    summary: Option<String>,

    #[arg(short = 'm', long, help = "Set the body of the commit")]
    message: Option<String>,

    #[arg(short, long, help = "Suppress the print of the complete message")]
    quiet: bool,
}

impl Subcommand for CommitSubCommand {
    fn execute(&self) -> i32 {
        let git_cli = GitCli::new();
        let output_manager = MessageEgressManagerImpl::new();
        if let Err(e) = git_cli.git_dir() {
            output_manager.error(&format!("Failed to retrieve git directory: {}", e));
            output_manager.error("commit subcommand can only be run inside a git project.");
            return 1;
        }
        let gitextra_manager = GitExtraManagerImpl::new(&git_cli);
        let prompt_manager = CommitPromptHelper::new(&gitextra_manager, &gitextra_manager);
        let options = match self.ask_missing_fields(prompt_manager) {
            Ok(o) => o,
            Err(e) => {
                output_manager.error(&e.to_string());
                return 1;
            }
        };
        let controller = CommitController::new(options, &git_cli, &output_manager);
        match controller.commit() {
            ControllerExitCode::Ok => 0,
            ControllerExitCode::Error(i) => i,
        }
    }
}

impl CommitSubCommand {
    fn breaking_option(&self) -> Option<bool> {
        match (self.is_breaking, self.is_not_breaking) {
            (false, false) => None,
            (false, true) => Some(false),
            (true, false) => Some(true),
            (true, true) => unreachable!(),
        }
    }

    fn ask_missing_fields(
        &self,
        prompt_manager: CommitPromptHelper,
    ) -> Result<CommitOptions, AnyError> {
        let temp_type = match self.commit_type.clone() {
            Some(t) => t,
            None => prompt_manager.ask_type()?,
        };
        let temp_scope = match self.scope.clone() {
            Some(s) if !s.is_empty() => Some(s),
            Some(_) => None,
            None => prompt_manager.ask_scope()?,
        };
        let temp_breaking = match self.breaking_option() {
            Some(b) => b,
            None => prompt_manager.ask_breaking()?,
        };
        let temp_summary = match self.summary.clone() {
            Some(s) => s,
            None => prompt_manager.ask_summary()?,
        };
        let temp_message = match self.message.clone() {
            Some(s) if !s.is_empty() => Some(s),
            Some(_) => None,
            None => prompt_manager.ask_body()?,
        };
        CommitOptions::new(
            temp_type,
            temp_scope,
            temp_breaking,
            temp_summary,
            temp_message,
            self.quiet,
        )
    }
}