1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::{error::Error, process};

use console::style;
use mit_commit::CommitMessage;

use crate::{
    console::exit::Code::{InitialNotMatchedToAuthor, UnparsableAuthorFile},
    lints::Problem,
};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum Code {
    InitialNotMatchedToAuthor = 3,
    UnparsableAuthorFile,
    StaleAuthor,
    DuplicatedTrailers,
    PivotalTrackerIdMissing,
    JiraIssueKeyMissing,
    GitHubIdMissing,
    SubjectNotSeparateFromBody,
    SubjectLongerThan72Characters,
    SubjectNotCapitalized,
    SubjectEndsWithPeriod,
    BodyWiderThan72Characters,
    NotConventionalCommit,
    NotEmojiLog,
}

pub fn unparsable_author(parse_err: &dyn Error) {
    super::style::problem(
        "Unable to parse the author config",
        &format!(
            "You can fix this by correcting the file so it's parsable\n\nYou can see a parsable \
             example by running:\ngit mit-config mit example\n\nHere's the technical details, \
             that might help you track down the source of the problem\n\n{}",
            parse_err
        ),
    );
    std::process::exit(UnparsableAuthorFile as i32);
}

pub fn initial_not_matched_to_author(initials_without_authors: &[&str]) {
    let mut tips = vec![
        "To see a summary of your configured authors run",
        "git mit-config mit generate",
        "To add a new author run",
        "git mit-config mit set eg \"Egg Sample\" egg.sample@example.com",
    ];

    if initials_without_authors.contains(&"config") {
        tips.push("Did you mean \"git mit-config\"");
    }

    if initials_without_authors.contains(&"relates-to") {
        tips.push("Did you mean \"git mit-relates-to\"");
    }

    if initials_without_authors.contains(&"install") {
        tips.push("Did you mean \"git mit-install\"");
    }

    super::style::problem(
        &format!(
            "Could not find the initials {}.",
            initials_without_authors.join(", ")
        ),
        &tips.join("\n\n"),
    );
    std::process::exit(InitialNotMatchedToAuthor as i32);
}

pub fn stale_author() {
    crate::console::style::problem(
        "The details of the author of this commit are stale",
        "Can you confirm who's currently coding?\n\nIt's nice to get and give the right \
         credit.\n\nYou can fix this by running `git mit` then the initials of whoever is coding \
         for example:\ngit mit bt\ngit mit bt se\n",
    );

    process::exit(Code::StaleAuthor as i32);
}

fn format_lint_problems(
    original_message: &CommitMessage,
    lint_problems: Vec<Problem>,
) -> Option<(String, Code)> {
    let (_, message_and_code) = lint_problems.into_iter().fold(
        (original_message, None),
        |(commit_message, output), problem| {
            (
                commit_message,
                match output {
                    Some((existing_output, _)) => Some((
                        {
                            let error = style(problem.error()).red().bold();
                            let tip = style(problem.tip()).italic();

                            format!("{}\n\n{}\n\n{}", existing_output, error, tip)
                        },
                        *(problem.code()),
                    )),
                    None => Some((
                        {
                            let error = style(problem.error()).red().bold();
                            let tip = style(problem.tip()).italic();

                            format!(
                                "{}\n\n---\n\n{}\n\n{}",
                                String::from(commit_message.clone()),
                                error,
                                tip
                            )
                        },
                        *(problem.code()),
                    )),
                },
            )
        },
    );
    message_and_code
}

pub fn lint_problem(commit_message: &CommitMessage, lint_problems: Vec<Problem>, clipboard: bool) {
    let output = format_lint_problems(commit_message, lint_problems);

    if let Some((message, exit_code)) = output {
        display_lint_err_and_exit(&message, exit_code, clipboard);
    }
}

fn display_lint_err_and_exit(commit_message: &str, exit_code: Code, clipboard: bool) {
    eprintln!("{}", commit_message);

    if clipboard {
        eprintln!(
            "\n{}",
            style("Your previous commit message has been copied to the clipboard")
                .bold()
                .blue()
        );
    }

    std::process::exit(exit_code as i32);
}