glint 6.3.4

a friendly tool for creating commits in the commitlint style
Documentation
use crate::cli;
use crossterm::{self as ct, cursor, terminal};
use glint::{prompt, Commit, Config, Git};
use std::io::Write as _Write;

fn with_raw<R>(f: impl FnOnce() -> R) -> R {
    let result = match terminal::enable_raw_mode() {
        Err(_) => {
            eprintln!("Failed to convert stdio to raw mode. Can't continue.");
            std::process::exit(1);
        }
        Ok(_) => f(),
    };

    let _r = terminal::disable_raw_mode();

    result
}

fn exit<R>(code: i32) -> Option<R> {
    let _r = terminal::disable_raw_mode();
    std::process::exit(code)
}

pub fn commit(params: cli::Commit, config: Config) {
    let git = match Git::from_cwd() {
        Ok(git) => git,
        Err(err) => {
            eprintln!("{}", err);
            std::process::exit(1);
        }
    };

    enum Stage {
        Files,
        Type,
        Scope(String),
        Message(String, Option<String>),
        Complete(String, Option<String>, String),
    }

    let mut stage = Stage::Type;

    let git_status = git.status().ok();

    if let Some(ref git_status) = git_status {
        let any_staged = git_status.any_staged();
        let any_unstaged = git_status.any_unstaged();

        if !any_staged && any_unstaged {
            if params.git_args.is_empty() {
                stage = Stage::Files;
            }
        } else if !any_staged {
            eprintln!("No changes to commit.");
            std::process::exit(1);
        }
    }

    let mut commit_files: Option<Vec<String>> = None;

    let mut escape_clear_lines = 0;

    loop {
        match stage {
            Stage::Files => {
                commit_files = with_raw(|| {
                    match prompt::FilesPrompt::new(&config, &git, git_status.clone().unwrap()).run()
                    {
                        prompt::FilesPromptResult::Files(files) => Some(files),
                        prompt::FilesPromptResult::Terminate => exit(2),
                        prompt::FilesPromptResult::Escape => exit(0),
                    }
                });

                stage = Stage::Type;
            }
            Stage::Type => {
                let ty = match params.ty {
                    Some(ref ty) => Some(ty.to_string()),
                    None => with_raw(|| match prompt::TypePrompt::new(&config).run() {
                        prompt::TypePromptResult::Type(ty) => Some(ty),
                        prompt::TypePromptResult::Terminate => exit(2),
                        prompt::TypePromptResult::Escape => None,
                    }),
                };

                let ty = match ty {
                    Some(s) => s,
                    None => {
                        stage = Stage::Files;
                        continue;
                    }
                };

                stage = Stage::Scope(ty);
            }
            Stage::Scope(ty) => {
                let scope = match params.scope {
                    Some(ref scope) => Some((Some(scope.to_string()), 0)),
                    None => with_raw(|| match prompt::ScopePrompt::new(&config, &ty).run() {
                        prompt::ScopePromptResult::Scope(scope, lines) => Some((scope, lines)),
                        prompt::ScopePromptResult::Terminate => exit(2),
                        prompt::ScopePromptResult::Escape => None,
                    }),
                };

                let (scope, lines) = match scope {
                    Some(t) => t,
                    None => {
                        stage = Stage::Type;
                        continue;
                    }
                };

                stage = Stage::Message(ty, scope);
                escape_clear_lines = lines as u16;
            }
            Stage::Message(ty, scope) => {
                let message = match params.message {
                    Some(ref message) => Some(message.to_string()),
                    None => with_raw(|| match prompt::MessagePrompt::new(&config).run() {
                        prompt::MessagePromptResult::Message(message) => Some(message),
                        prompt::MessagePromptResult::Terminate => exit(2),
                        prompt::MessagePromptResult::Escape => None,
                    }),
                };

                let message = match message {
                    Some(s) => s,
                    None => {
                        stage = Stage::Scope(ty);

                        let mut stderr = std::io::stderr();
                        ct::queue!(
                            stderr,
                            cursor::MoveUp(escape_clear_lines),
                            terminal::Clear(terminal::ClearType::FromCursorDown)
                        )
                        .unwrap();

                        continue;
                    }
                };

                stage = Stage::Complete(ty, scope, message);
            }
            Stage::Complete(ty, scope, message) => {
                if let Some(commit_files) = commit_files {
                    let _r = git.add(commit_files).status();
                }

                let commit = Commit { ty, scope, message };

                let git_message = commit.build_message();

                match git.commit(&git_message, params.git_args).status() {
                    Ok(status) if status.success() => println!("Commit successful."),
                    Ok(status) => match status.code() {
                        Some(code) => {
                            eprintln!("Commit command failed with {}", code);
                            std::process::exit(code);
                        }
                        None => {
                            eprintln!("Commit command failed with no status. Was likely killed by another process.");
                            std::process::exit(1);
                        }
                    },
                    Err(err) => {
                        eprintln!(
                            "Failed to run git. This is the best error I have:\n{:?}",
                            err
                        );
                        std::process::exit(1);
                    }
                };

                return;
            }
        }
    }
}