diff --git a/scripts/commit-msg.crs b/scripts/commit-msg.crs new file mode 100755 index 0000000..a873246 --- /dev/null +++ b/scripts/commit-msg.crs @@ -0,0 +1,95 @@ +#!/usr/bin/env run-cargo-script +// cargo-deps: regex="1.3.9" +use std::{ env, fs, process }; +use std::path::PathBuf; +use std::process::Command; + +fn main() { + let arg1 = env::args().nth(1).unwrap_or_else(|| { + exit_with_error_message("Commit message is EMPTY!"); + }); + + let is_install = arg1 == "install" || arg1 == "INSTALL"; + let is_force_install = arg1 == "forceinstall" || arg1 == "FORCEINSTALL" + || arg1 == "force-install" || arg1 == "FORCE-INSTALL" + || arg1 == "force_install" || arg1 == "FORCE_INSTALL"; + if is_install || is_force_install { + install_commit_msg(is_force_install); return; + } + + let commit_message = fs::read_to_string(arg1).unwrap_or_else(|e| { + exit_with_error_message(&format!("Read commit message failed: {}", e)); + }); + + let re = regex::Regex::new("^(feat|fix|docs|style|refactor|test|chore)(\\([\\w\\-_]+\\))?:\\s*.*").unwrap(); + let is_commit_message_matches = re.is_match(&commit_message); + + print_info(&format!("Commit message: {}{}{}", UNDER, commit_message.trim(), END)); + if is_commit_message_matches { + print_ok("Commit message rule matches"); + } else { + print_info(&format!(r#"Please follow the commit spec: +feat: add hat wobble +^--^ ^------------^ +| | +| +-> Summary in present tense. +| ++-------> Type: chore, docs, feat, fix, refactor, style, or test. + +feat : new feature for the user, not a new feature for build script +fix : bug fix for the user, not a fix to a build script +docs : changes to the documentation +style : formatting, missing semi colons, etc; no production code change +refactor : refactoring production code, eg. renaming a variable +test : adding missing tests, refactoring tests; no production code change +chore : updating grunt tasks etc; no production code change + +Reference: {}https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716{} +"#, UNDER, END)); + exit_with_error_message("Commit message rule is NOT matched"); + } +} + +const RED: &str = "\x1B[91m"; +const GREEN: &str = "\x1B[92m"; +const BOLD: &str = "\x1B[1m"; +const UNDER: &str = "\x1B[4m"; +const END: &str = "\x1B[0m"; + +fn install_commit_msg(force: bool) { + let home_path = PathBuf::from(env::var("HOME").unwrap_or_else(|e| { + exit_with_error_message(&format!("Get env HOME failed: {}!", e)); + })); + let commit_msg_crs = home_path.join("bin").join("commit-msg.crs"); + if !commit_msg_crs.exists() { + exit_with_error_message(&format!("File {:?} NOT exists!", commit_msg_crs)); + } + + let git_hooks = PathBuf::from(".git").join("hooks"); + if !git_hooks.exists() { + exit_with_error_message(&format!("Path {:?} NOT exists!", git_hooks)); + } + let git_hooks_commit_msg = git_hooks.join("commit-msg"); + if git_hooks_commit_msg.exists() && !force { + exit_with_error_message(&format!("File {:?} exists! or try forceinstall.", git_hooks_commit_msg)); + } + + let commit_msg_crs_content = fs::read_to_string(&commit_msg_crs).unwrap_or_else(|e| { + exit_with_error_message(&format!("Read file: {:?} failed: {}.", commit_msg_crs, e)); + }); + fs::write(&git_hooks_commit_msg, commit_msg_crs_content).unwrap_or_else(|e| { + exit_with_error_message(&format!("Write file: {:?} failed: {}.", git_hooks_commit_msg, e)); + }); + Command::new("chmod").args(&["+x", git_hooks_commit_msg.to_str().unwrap()]).output().unwrap_or_else(|e| { + exit_with_error_message(&format!("Apply executable permission on file: {:?} failed: {}.", git_hooks_commit_msg, e)); + }); + + print_ok("Install commit-msg to repo successed!"); +} + +fn exit_with_error() -> ! { process::exit(1) } +fn exit_with_error_message(msg: &str) -> ! { print_error(msg); exit_with_error() } + +fn print_info(msg: &str) { println!("{}[INFO ]{} {}", BOLD, END, msg); } +fn print_ok(msg: &str) { println!("{}{}[OK ]{} {}{}{}", GREEN, BOLD, END, GREEN, msg, END); } +fn print_error(msg: &str) { println!("{}{}[ERROR]{} {}{}{}", RED, BOLD, END, RED, msg, END); }