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
use serde::{Serialize, Deserialize};
use dialoguer::{Confirm, Editor};
use std::fmt::{self, Display, Formatter};
use std::fs::read_to_string;
use std::io::Result;

#[derive(Serialize, Deserialize)]
pub struct CommitType {
    text: String,
    description: String,
}

impl CommitType {
    pub fn new(text: &str, description: &str) -> Self {
        Self { text: text.to_string(), description: description.to_string() }
    }
}

impl Display for CommitType {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{:9}: {}", self.text, self.description)
    }
}

pub fn get_default_commit_types() -> Vec<CommitType> {
    vec![
        CommitType::new("feat", "A new feature"),
        CommitType::new("fix", "A bug fix"),
        CommitType::new("docs", "Documentation only changes"),
        CommitType::new("style", "Changes that do not affect the meaning of the code(white-space, fomatting, missing semi-colons, etc)"),
        CommitType::new("refactor", "A code change that neither fixes a bug or adds a feature"),
        CommitType::new("perf", "A code change that improves performance"),
        CommitType::new("test", "Adding missing tests"),
        CommitType::new("chore", "Change to the build process or auxiliary tools and libraries such as documentation generation"),
    ]
}

pub fn get_cm_types_from_file() -> Result<Vec<CommitType>> {
    let f = read_to_string("commit-format.json")?;
    let cm_types: Vec<CommitType> = serde_json::from_str(&f)?;
    Ok(cm_types)
}

pub fn get_optional_commit_body_and_footer() -> Option<String> {
    let should_open_editor = Confirm::new()
        .with_prompt("Do you want to write a long description?")
        .default(false)
        .show_default(false)
        .interact()
        .unwrap();
    if should_open_editor {
        return Editor::new().edit("").unwrap();
    }
    None
}

pub fn put_together_commit_message(
    commit_type: &CommitType,
    scope: String,
    subject: String,
    optional_body_and_footer: Option<String>,
) -> String {
    let mut format_commit_message = commit_type.text.to_string();
    if scope.is_empty() {
        format_commit_message.push_str(": ");
    } else {
        format_commit_message.push_str(&format!("({}): ", scope));
    }
    format_commit_message.push_str(&subject);
    if let Some(text) = optional_body_and_footer {
        format_commit_message.push_str(&format!("\n\n{}", text));
    }
    format_commit_message
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_commit_to_string() {
        let fix = CommitType::new("fix", "just for test");
        assert_eq!(fix.to_string(), String::from("fix      : just for test"));
    }

    #[test]
    fn test_composite_commit() {
        let bug = CommitType::new("bug", "a test");
        let scope = String::from("view");
        let subject = String::from("test example");
        let other: Option<String> = None;
        let result = put_together_commit_message(&bug, scope, subject, other);
        assert_eq!(result, String::from("bug(view): test example"))
    }
}