diff --git a/Cargo.lock b/Cargo.lock index edbd684..ba43259 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,54 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anstream" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -20,10 +68,57 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "commit-helper" -version = "0.1.4" +version = "0.2.0" dependencies = [ + "clap", "inquire", ] @@ -58,6 +153,12 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "inquire" version = "0.6.2" @@ -111,7 +212,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -143,7 +244,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -215,6 +316,12 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "2.0.39" @@ -264,6 +371,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -298,7 +411,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -307,13 +429,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -322,38 +459,80 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml index 7520859..c7207c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "commit-helper" -version = "0.1.4" +version = "0.2.0" edition = "2021" [[bin]] @@ -8,4 +8,5 @@ name = "ch" path = "src/main.rs" [dependencies] +clap = { version = "4.4.12", features = ["derive"] } inquire = { version = "0.6.2" } diff --git a/README.md b/README.md index d289423..f5a752b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ # commit-helper (ch) -A simple tool to help write commit messages and run git commands. +A simple tool to help run commands related to `git commit` in one go. ![Demo](demo.gif) -You may read a brief post about commit-helper on my website: [mueezkhan.com/memos/commit-helper](https://www.mueezkhan.com/memos/commit-helper) - ## Installation Make sure you have [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) installed, then run the following command in your terminal: @@ -14,49 +12,82 @@ Make sure you have [Cargo](https://doc.rust-lang.org/cargo/getting-started/insta cargo install --git https://github.com/rzmk/commit-helper ``` -## Usage +## Reference -To run the tool, simply run `ch` in your terminal: +To run the interactive workflow, simply run `ch` in your terminal: ```bash ch ``` -### `-a` +### `--help` or `-h` + +To get the help message, run `ch --help` or `ch -h`: + +```bash +ch -h +``` + +### `--add` or `-a` -If you want to run `git add -A` before committing, use the `-a` flag: +If you want to run `git add -A` before committing, use the `--add` or `-a` flag: ```bash ch -a ``` -### `-p` +### `--push` or `-p` -If you want to run `git push` after committing, use the `-p` flag: +If you want to run `git push` after committing, use the `--push` or `-p` flag: ```bash ch -p ``` -### `--dry-run` or `-d` +### `--message` or `-m` + +If you want to pass in a custom commit message (therefore skipping the interactive steps), use the `--message` or `-m` flag: + +```bash +ch -m "feat: add new feature" +``` + +### `--debug` or `-d` -If you want to do a dry run without actually adding or committing, use the `-d` or `--dry-run` flag: +If you want to see the debug output, use the `--debug` or `-d` flag: + +```bash +ch -d +``` + +> Note: The debug output will not be printed if you use the `--dry-run` flag. + +### `--dry-run` + +If you want to do a dry run without executing any commands, use the `--dry-run` flag: ```bash ch --dry-run ``` -### `--debug` +## Example + +If I want to run `git add -A`, then `git commit -m "feat: add new feature"`, then `git push` all in one go, I could run the following command: + +```bash +ch -m "feat: add new feature" -a -p +``` -If you want to see the debug output, use the `--debug` flag: +Equivalently to the above command, I may instead combine the short flags: ```bash -ch --debug +ch -apm "feat: add new feature" ``` -Note that the debug output will not be printed if you use the `--dry-run` flag. +> Note: The order of the combined short flags does not matter, except for the `-m` flag, which must be the last flag if you want to pass in a custom commit message after a combination of flags. ## Tech Stack - [Rust](https://www.rust-lang.org/) +- [clap](https://github.com/clap-rs/clap) - [inquire](https://github.com/mikaelmello/inquire) diff --git a/src/main.rs b/src/main.rs index ebbb2dd..9b23530 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,55 +1,53 @@ -use inquire::{Confirm, Select}; -use std::{env, process::Command}; +use clap::Parser; +use inquire::{formatter::OptionFormatter, Confirm, InquireError, Select}; -fn print_debug_info(output: std::process::Output) { - println!("Debug info:"); - println!("stdout:\n{}", String::from_utf8_lossy(&output.stdout)); - println!("stderr:\n{}", String::from_utf8_lossy(&output.stderr)); - println!("Exit status:\n{}", output.status); -} +pub mod parser; +use parser::{Cli, CommitType}; +pub mod util; +use util::orchestrate_commit; fn main() { - // Check if -a flag is passed to run git add -A before commit - let args: Vec = env::args().collect(); - let run_git_add = args.len() > 1 && args.contains(&String::from("-a")); - // Check if -d or --dry-run flag is passed to run in dry run mode - let dry_run = args.len() > 1 - && (args.contains(&String::from("-d")) || args.contains(&String::from("--dry-run"))); - if dry_run { - println!("Running in dry run mode\n"); + // Parse command line arguments + let cli: Cli = Cli::parse(); + + // If a message was provided, run the commands in succession + if let Some(message) = &cli.message { + orchestrate_commit(&cli, &message); + return; } - // Check if -p flag is passed to run git push after commit - let run_git_push = args.len() > 1 && args.contains(&String::from("-p")); - // Check if --debug flag is passed to run in debug mode - let debug = args.len() > 1 && args.contains(&String::from("--debug")); - // Commit Type + // Otherwise, prompt for a message let commit_type_options = vec![ - "build - build system and dependencies", - "ci - continuous integration", - "chore - misc/maintenance not related to core code", - "docs - documentation changes (e.g., README.md, comments)", - "feat - new feature or significant enhancement", - "fix - bug fix or error correction", - "perf - performance improvement", - "refactor - code restructuring or cleanup", - "test - add or update tests", + CommitType::new("build", "build system and dependencies"), + CommitType::new("ci", "continuous integration"), + CommitType::new("chore", "misc/maintenance not related to core code"), + CommitType::new("docs", "documentation changes (e.g., README.md, comments)"), + CommitType::new("feat", "new feature or significant enhancement"), + CommitType::new("fix", "bug fix or error correction"), + CommitType::new("perf", "performance improvement"), + CommitType::new("refactor", "code restructuring or cleanup"), + CommitType::new("test", "add or update tests"), ]; - let commit_type = Select::new("Type:", commit_type_options).prompt(); - let commit_type = match commit_type { - Ok(commit_type) => - // Get the first word of the commit type - { - let commit_type = commit_type.split_whitespace().next().unwrap(); - commit_type - } - Err(_) => { - println!("No commit type selected, exiting"); + + // Format the commit type options for display + let formatter: OptionFormatter = + &|ct| format!("{}: {}", ct.value.name, ct.value.description); + + // Prompt for the commit type + let selected_type: Result = Select::new("Type:", commit_type_options) + .with_formatter(formatter) + .prompt(); + + // Get the name of the selected commit type + let commit_type = match selected_type { + Ok(ct) => ct.name, + Err(e) => { + println!("Error: {:?}", e); return; } }; - // Summary + // Prompt for the commit summary (the message after the commit type) let commit_summary = inquire::Text::new("Summary:").prompt(); let commit_summary = match commit_summary { Ok(commit_summary) => commit_summary, @@ -59,59 +57,21 @@ fn main() { } }; + // Format the commit message to include the commit type and summary let result_message = format!("{}: {}", commit_type, commit_summary); - // Confirm + // Confirm the commit message let confirm = - Confirm::new(format!("Result:\n\n{}\n\nCommit? (y/n):", result_message,).as_str()).prompt(); + Confirm::new(format!("Result:\n\n{}\n\nCommit? (y/n):", result_message).as_str()).prompt(); + // If confirmed, run the commands in succession match confirm { Ok(true) => { - if run_git_add { - println!("Running git add -A"); - if !dry_run { - let output = Command::new("git") - .args(["add", "-A"]) - .output() - .expect("failed to execute process"); - - if debug { - print_debug_info(output); - } - } - } - - println!("Running git commit -m \"{}\"", result_message); - if !dry_run { - let output = Command::new("git") - .args(["commit", "-m", result_message.as_str()]) - .output() - .expect("failed to execute process"); - - if debug { - print_debug_info(output); - } - } - - if run_git_push { - println!("Running git push"); - if !dry_run { - let output = Command::new("git") - .args(["push"]) - .output() - .expect("failed to execute process"); - - if debug { - print_debug_info(output); - } - } - } + orchestrate_commit(&cli, &result_message); } _ => { println!("Exiting"); return; } } - - println!("Done 🎉"); } diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..f0892c1 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,50 @@ +use clap::Parser; + +#[derive(Parser)] +#[command( + name = "commit-helper", + author = "Mueez Khan", + about = "Run multiple commands related to `git commit` in succession" +)] +pub struct Cli { + /// Run `git add -A` before committing + #[arg(short = 'a', long = "add")] + pub add: bool, + + /// Run `git push` after committing + #[arg(short = 'p', long = "push")] + pub push: bool, + + /// Run in debug mode (print output of each command) + #[arg(short = 'd', long = "debug")] + pub debug: bool, + + /// Run in dry run mode (disallow executing commands) + #[arg(long = "dry-run")] + pub dry_run: bool, + + /// Run `git commit` with the given message + /// (if not provided, will prompt for message) + #[arg(short = 'm', long = "message")] + pub message: Option, +} + +pub struct CommitType { + pub name: String, + pub description: String, +} + +impl CommitType { + pub fn new(name: &str, description: &str) -> Self { + Self { + name: name.to_owned(), + description: description.to_owned(), + } + } +} + +impl std::fmt::Display for CommitType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}: {}", self.name, self.description) + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..e453f1f --- /dev/null +++ b/src/util.rs @@ -0,0 +1,40 @@ +use super::parser::Cli; +use std::process::Command; + +// Run the commands in succession +pub fn orchestrate_commit(cli: &Cli, message: &str) { + if cli.add { + println!("Running git add -A"); + run(&cli, "git", &["add", "-A"]); + } + + println!("Running git commit -m \"{}\"", message); + run(&cli, "git", &["commit", "-m", message]); + + if cli.push { + println!("Running git push"); + run(&cli, "git", &["push"]); + } + + println!("Done 🎉"); +} + +pub fn print_debug_info(output: std::process::Output) { + println!("Debug info:"); + println!("stdout:\n{}", String::from_utf8_lossy(&output.stdout)); + println!("stderr:\n{}", String::from_utf8_lossy(&output.stderr)); + println!("Exit status:\n{}", output.status); +} + +pub fn run(cli: &Cli, command: &str, args: &[&str]) { + if !cli.dry_run { + let output = Command::new(command) + .args(args) + .output() + .expect("failed to execute process"); + + if cli.debug { + print_debug_info(output); + } + } +}