#!/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; use std::os::unix::fs::PermissionsExt; /// Git commit message format check util /// /// Commit format MUST be (): subject /// /// Usage: /// `commit-msg.crs usage` /// /// Install: /// `commit-msg.crs install` fn main() { let arg1 = env::args().nth(1).unwrap_or_else(|| { exit_with_error_message("Commit message is EMPTY!"); }); if arg1 == "usage" || arg1 == "USAGE" { print_usage(); return; } let is_install = arg1 == "install" || arg1 == "INSTALL"; let is_force_install = arg1 == "forceinstall" || arg1 == "FORCEINSTALL" || arg1 == "force-install" || arg1 == "FORCE-INSTALL" || arg1 == "installforce" || arg1 == "INSTALLFORCE" || arg1 == "install-force" || arg1 == "INSTALL-FORCE"; 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_usage(); exit_with_error_message("Commit message rule is NOT matched"); } } const RED: &str = "\x1B[91m"; const GREEN: &str = "\x1B[92m"; const YELLOW: &str = "\x1B[93m"; const BOLD: &str = "\x1B[1m"; const UNDER: &str = "\x1B[4m"; const END: &str = "\x1B[0m"; fn print_usage() { print_info(&format!(r#"Please follow the commit spec: Format: {b}{e}(){b}: {e} is optional {b}feat{e}: add hat wobble ^--^ ^------------^ | | | +-> Summary in present tense. | +-------> Type: chore, docs, feat, fix, refactor, style, or test. {b}feat{e} : new feature for the user, not a new feature for build script {b}fix{e} : bug fix for the user, not a fix to a build script {b}docs{e} : changes to the documentation {b}style{e} : formatting, missing semi colons, etc; no production code change {b}refactor{e} : refactoring production code, eg. renaming a variable {b}test{e} : adding missing tests, refactoring tests; no production code change {b}chore{e} : updating grunt tasks etc; no production code change Reference: {u}https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716{e} or mirror: {u}https://hatter.ink/wiki/view_raw.action?__access_token=PUBLIC&id=42{e} "#, b = BOLD, e = END, u = UNDER)); } 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"); let commig_msg_exec_file = if !commit_msg_crs.exists() { print_warn(&format!("File {:?} NOT exists!", commit_msg_crs)); PathBuf::from(env::args().next().unwrap()) } else { 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)); } print_info(&format!("Copy file: {:?} to: {:?}", commig_msg_exec_file, git_hooks_commit_msg)); let commig_msg_exec_file_content = fs::read(&commig_msg_exec_file).unwrap_or_else(|e| { exit_with_error_message(&format!("Read file: {:?} failed: {}.", commig_msg_exec_file, e)); }); fs::write(&git_hooks_commit_msg, commig_msg_exec_file_content).unwrap_or_else(|e| { exit_with_error_message(&format!("Write file: {:?} failed: {}.", git_hooks_commit_msg, e)); }); fs::set_permissions(&git_hooks_commit_msg, PermissionsExt::from_mode(0o755)).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!("{b}[INFO ]{e} {m}", b = BOLD, e = END, m = msg); } fn print_ok(msg: &str) { println!("{g}{b}[OK ]{e} {g}{m}{e}", g = GREEN, b = BOLD, e = END, m = msg); } fn print_warn(msg: &str) { println!("{y}{b}[WARN ]{e} {y}{m}{e}", y = YELLOW, b = BOLD, e = END, m = msg); } fn print_error(msg: &str) { println!("{r}{b}[ERROR]{e} {r}{m}{e}", r = RED, b = BOLD, e = END, m = msg); }