diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..537d1dc --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +liberapay: alt-art diff --git a/Cargo.lock b/Cargo.lock index 4904532..d0d2dc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,15 +13,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.53" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" +checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" [[package]] name = "assert_fs" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633ff1df0788db09e2087fb93d05974e93acb886ac3aec4e67be1d6932e360e4" +checksum = "cf09bb72e00da477c2596865e8873227e2196d263cca35414048875dbbeea1be" dependencies = [ "doc-comment", "globwalk", @@ -71,9 +71,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "3.0.14" +version = "3.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62" +checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123" dependencies = [ "atty", "bitflags", @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.0.5" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a0645a430ec9136d2d701e54a95d557de12649a9dd7109ced3187e648ac824" +checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" dependencies = [ "heck", "proc-macro-error", @@ -101,7 +101,7 @@ dependencies = [ [[package]] name = "commit" -version = "0.3.1" +version = "0.4.0" dependencies = [ "anyhow", "assert_fs", @@ -630,9 +630,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ "itoa", "ryu", @@ -723,9 +723,9 @@ checksum = "13a4ec180a2de59b57434704ccfad967f789b12737738798fa08798cd5824c16" [[package]] name = "textwrap" -version = "0.14.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" diff --git a/Cargo.toml b/Cargo.toml index fb223d3..efcb9c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,13 @@ [package] name = "commit" -version = "0.3.1" +version = "0.4.0" edition = "2021" homepage = "https://github.com/alt-art/commit" repository = "https://github.com/alt-art/commit" documentation = "https://github.com/alt-art/commit/wiki" description = "A tool to make patterned (conventional) commit messages" +categories = ["development-tools", "command-line-utilities"] +keywords = ["git", "commit","message", "conventional"] authors = ["Pedro H. M. "] readme = "README.md" license = "MIT" @@ -14,13 +16,13 @@ license = "MIT" [dependencies] inquire = "0.2.1" serde = { version = "1.0.136", features = ["derive"] } -serde_json = "1.0.78" -clap = { version = "3.0.14", features = ["derive"] } +serde_json = "1.0.79" +clap = { version = "3.1.6", features = ["derive"] } dirs = "4.0.0" -anyhow = "1.0.53" +anyhow = "1.0.56" [dev-dependencies] -assert_fs = "1.0.6" +assert_fs = "1.0.7" [package.metadata.deb] name = "commit" diff --git a/src/commit.rs b/src/commit.rs new file mode 100644 index 0000000..c68d9e1 --- /dev/null +++ b/src/commit.rs @@ -0,0 +1,55 @@ +use anyhow::{anyhow, Result}; + +use std::fs; +use std::io::Write; +use std::path::PathBuf; +use std::process::{exit, Command}; + +pub fn commit_as_hook(commit_message: &str) -> Result<()> { + let output = Command::new("git") + .args(&["rev-parse", "--absolute-git-dir"]) + .output(); + match output { + Ok(output) => { + if !output.status.success() { + return Err(anyhow!("Could not get git directory")); + } + let git_dir = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim()); + let commit_file_path = git_dir.join("COMMIT_EDITMSG"); + fs::write(commit_file_path, commit_message)?; + } + Err(e) => { + return Err(anyhow!( + "Failed to run git. Make sure git is installed\nAdditional info: {}", + e + )); + } + } + Ok(()) +} + +pub fn commit(commit_message: &str) -> Result<()> { + let output = Command::new("git") + .arg("commit") + .arg("-m") + .arg(commit_message) + .output(); + match output { + Ok(output) => { + std::io::stdout().write_all(&output.stdout)?; + std::io::stderr().write_all(&output.stderr)?; + exit( + output + .status + .code() + .ok_or_else(|| anyhow!("Could not get exit code"))?, + ); + } + Err(e) => { + return Err(anyhow::anyhow!( + "Failed to run git. Make sure git is installed\nAdditional info: {}", + e + )); + } + }; +} diff --git a/src/commit_message/message_build/mod.rs b/src/commit_message/message_build/mod.rs index f386b03..fd99301 100644 --- a/src/commit_message/message_build/mod.rs +++ b/src/commit_message/message_build/mod.rs @@ -9,8 +9,8 @@ pub struct MessageBuilder { } impl MessageBuilder { - pub fn new(config: Config) -> MessageBuilder { - MessageBuilder { + pub const fn new(config: Config) -> Self { + Self { config, message: String::new(), } diff --git a/src/commit_message/message_build/tests.rs b/src/commit_message/message_build/tests.rs index e942c16..2f71286 100644 --- a/src/commit_message/message_build/tests.rs +++ b/src/commit_message/message_build/tests.rs @@ -1,6 +1,5 @@ use crate::commit_message::message_build::MessageBuilder; use crate::config::Config; -use anyhow::Result; fn message_with_config(config: Config) -> String { let mut builder = MessageBuilder::new(config); @@ -13,11 +12,11 @@ fn message_with_config(config: Config) -> String { } #[test] -fn message_builder_config_test() -> Result<()> { +fn message_builder_config_test() { let mut config = Config { - scope_prefix: "(".to_string(), - scope_suffix: ")".to_string(), - subject_separator: ": ".to_string(), + scope_prefix: "(".to_owned(), + scope_suffix: ")".to_owned(), + subject_separator: ": ".to_owned(), type_prefix: None, type_suffix: None, }; @@ -27,36 +26,35 @@ fn message_builder_config_test() -> Result<()> { "feat(test): description\n\nbody\n\nfooter" ); - config.type_prefix = Some("[".to_string()); - config.type_suffix = Some("]".to_string()); + config.type_prefix = Some("[".to_owned()); + config.type_suffix = Some("]".to_owned()); assert_eq!( message_with_config(config.clone()), "[feat](test): description\n\nbody\n\nfooter" ); - config.subject_separator = " ".to_string(); + config.subject_separator = " ".to_owned(); assert_eq!( message_with_config(config.clone()), "[feat](test) description\n\nbody\n\nfooter" ); - config.scope_prefix = "".to_string(); - config.scope_suffix = "".to_string(); + config.scope_prefix = "".to_owned(); + config.scope_suffix = "".to_owned(); assert_eq!( - message_with_config(config.clone()), + message_with_config(config), "[feat]test description\n\nbody\n\nfooter" ); - Ok(()) } #[test] -fn message_builder_test() -> Result<()> { +fn message_builder_test() { let config = Config { - scope_prefix: "(".to_string(), - scope_suffix: ")".to_string(), - subject_separator: ": ".to_string(), + scope_prefix: "(".to_owned(), + scope_suffix: ")".to_owned(), + subject_separator: ": ".to_owned(), type_prefix: None, type_suffix: None, }; @@ -76,5 +74,4 @@ fn message_builder_test() -> Result<()> { builder.set_footer("footer"); assert_eq!(builder.message, "feat(test): description\n\nbody\n\nfooter"); - Ok(()) } diff --git a/src/commit_message/mod.rs b/src/commit_message/mod.rs index 281b39c..535ad84 100644 --- a/src/commit_message/mod.rs +++ b/src/commit_message/mod.rs @@ -10,19 +10,19 @@ use prompt::Prompt; pub fn make_message_commit(pattern: CommitPattern) -> Result { let mut message_inquirer = MessageInquirer::new(pattern.clone()); let skip_commit = pattern.skip_commit; - if !skip_commit.contains(&"commit_type".to_string()) { + if !skip_commit.contains(&"commit_type".to_owned()) { message_inquirer.type_choice()?; } - if !skip_commit.contains(&"commit_scope".to_string()) { + if !skip_commit.contains(&"commit_scope".to_owned()) { message_inquirer.scope_choice()?; } - if !skip_commit.contains(&"commit_description".to_string()) { + if !skip_commit.contains(&"commit_description".to_owned()) { message_inquirer.description()?; } - if !skip_commit.contains(&"commit_body".to_string()) { + if !skip_commit.contains(&"commit_body".to_owned()) { message_inquirer.body()?; } - if !skip_commit.contains(&"commit_footer".to_string()) { + if !skip_commit.contains(&"commit_footer".to_owned()) { message_inquirer.footer()?; } message_inquirer.message() @@ -36,7 +36,7 @@ struct MessageInquirer { impl MessageInquirer { fn new(pattern: CommitPattern) -> Self { - MessageInquirer { + Self { commit_builder: MessageBuilder::new(pattern.config.clone()), prompt: Prompt::new(), pattern, diff --git a/src/commit_message/prompt.rs b/src/commit_message/prompt.rs index b168068..4e9a04e 100644 --- a/src/commit_message/prompt.rs +++ b/src/commit_message/prompt.rs @@ -13,11 +13,11 @@ pub struct Prompt { } impl Prompt { - pub fn new() -> Prompt { + pub fn new() -> Self { let default = RenderConfig::default(); let prompt_prefix = Styled::new("-").with_fg(Color::LightGreen); let current_config = default.with_prompt_prefix(prompt_prefix); - Prompt { + Self { config: current_config, } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 6b3d9d8..a945cd7 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,8 +4,7 @@ mod tests; use anyhow::{anyhow, Context, Result}; use serde::Deserialize; use std::fmt::{Display, Formatter, Result as FmtResult}; -use std::fs::File; -use std::io::Read; +use std::fs; use std::path::Path; use std::path::PathBuf; @@ -52,9 +51,7 @@ pub struct CommitPattern { } fn get_config_path_content(config_path: impl AsRef) -> Result { - let mut file = File::open(config_path)?; - let mut content = String::new(); - file.read_to_string(&mut content)?; + let content = fs::read_to_string(config_path)?; Ok(content) } @@ -87,7 +84,7 @@ fn get_config_path() -> Result { Ok(current_file) } else { let config_file = dirs::config_dir() - .ok_or(anyhow!("Could not find config directory"))? + .ok_or_else(|| anyhow!("Could not find config directory"))? .join("commit/commit.json"); Ok(config_file) } @@ -97,6 +94,6 @@ pub fn get_pattern(config_path: Option) -> Result { let default_pattern_str = DEFAULT_CONFIG_FILE; let selected_config_path = select_custom_config_path(config_path)?; let pattern_str = get_config_path_content(&selected_config_path) - .unwrap_or_else(|_| default_pattern_str.to_string()); + .unwrap_or_else(|_| default_pattern_str.to_owned()); serde_json::from_str(&pattern_str).context("Failed to parse commit pattern from file") } diff --git a/src/config/tests.rs b/src/config/tests.rs index 3cad57b..c512a24 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -18,16 +18,18 @@ fn select_custom_config_path_test() -> Result<()> { let selected_config_path = select_custom_config_path(Some(PathBuf::new())); match selected_config_path { Err(err) => assert_eq!(err.to_string(), "Config file does not exist: "), - _ => assert!(false), + _ => unreachable!(), } Ok(()) } #[test] fn get_config_path_test() -> Result<()> { - let config_file = dirs::config_dir().unwrap().join("commit/commit.json"); + let config_file = dirs::config_dir() + .ok_or_else(|| anyhow!("Could not find config directory"))? + .join("commit/commit.json"); let config_path = get_config_path(); - assert_eq!(config_file.to_str(), config_path.unwrap().to_str()); + assert_eq!(config_file.to_str(), config_path?.to_str()); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 8dbb7d4..7e36b95 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,21 @@ +#![warn( + clippy::all, + clippy::pedantic, + clippy::nursery, + clippy::cargo, + clippy::str_to_string +)] +#![allow(clippy::module_name_repetitions)] + +mod commit; mod commit_message; mod config; use anyhow::Result; use clap::Parser; use std::io::Write; + use std::path::PathBuf; -use std::process::{exit, Command}; use commit_message::make_message_commit; @@ -23,6 +33,8 @@ struct Opt { config: Option, #[clap(long, help = "Init custom configuration file")] init: bool, + #[clap(short, long, help = "Use as hook")] + hook: bool, } fn main() -> Result<()> { @@ -36,28 +48,17 @@ fn main() -> Result<()> { if opt.init { let mut file = std::fs::File::create("commit.json")?; file.write_all(DEFAULT_CONFIG_FILE.as_bytes())?; - Ok(()) - } else { - let pattern = config::get_pattern(opt.config)?; - let commit_message = make_message_commit(pattern)?; - - let output = Command::new("git") - .arg("commit") - .arg("-m") - .arg(commit_message) - .output(); - match output { - Ok(output) => { - std::io::stdout().write_all(&output.stdout).unwrap(); - std::io::stderr().write_all(&output.stderr).unwrap(); - exit(output.status.code().unwrap()); - } - Err(e) => { - return Err(anyhow::anyhow!( - "Failed to run git. Make sure git is installed\nAdditional info: {}", - e - )); - } - }; + return Ok(()); + } + + let pattern = config::get_pattern(opt.config)?; + let commit_message = make_message_commit(pattern)?; + + if opt.hook { + commit::commit_as_hook(&commit_message)?; + return Ok(()); } + + commit::commit(&commit_message)?; + Ok(()) }