From f0fb42b6f076e307127b41369444c30c1ec6d706 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Tue, 8 Apr 2025 08:38:04 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20+=20bootstrap?= =?UTF-8?q?=20ask=20order=20+=20shell=5Fquote?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 103 ++++++++++++++++++++++++++---------------- Cargo.toml | 3 +- src/bootstrap.rs | 16 +++++-- src/git.rs | 31 ++++++++++++- src/lib.rs | 102 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 113 ++++------------------------------------------- 6 files changed, 220 insertions(+), 148 deletions(-) create mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 117a50c..189642d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,9 +66,20 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] [[package]] name = "byteorder" @@ -84,18 +95,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.26" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.26" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" dependencies = [ "anstream", "anstyle", @@ -142,9 +153,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[package]] name = "either" @@ -181,13 +192,14 @@ dependencies = [ [[package]] name = "g" -version = "0.1.0" +version = "0.1.1" dependencies = [ "clap", "either", "inquire", "serde", "serde_json", + "shell-quote", "subprocess", "toml", "yansi", @@ -215,7 +227,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "crossterm", "dyn-clone", "fuzzy-matcher", @@ -234,15 +246,15 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "lock_api" @@ -256,9 +268,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -289,9 +301,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "parking_lot" @@ -318,36 +330,42 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] -name = "ryu" -version = "1.0.18" +name = "regex-automata" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "scopeguard" @@ -377,9 +395,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.137" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -396,6 +414,15 @@ dependencies = [ "serde", ] +[[package]] +name = "shell-quote" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb502615975ae2365825521fa1529ca7648fd03ce0b0746604e0683856ecd7e4" +dependencies = [ + "bstr", +] + [[package]] name = "signal-hook" version = "0.3.17" @@ -428,9 +455,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "strsim" @@ -450,9 +477,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.96" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -505,9 +532,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" @@ -696,9 +723,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 2c3ca22..ccaada4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "g" -version = "0.1.0" +version = "0.1.1" edition = "2024" [dependencies] @@ -9,6 +9,7 @@ either = { version = "1.15.0", features = ["serde"] } inquire = "0.7.5" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.137" +shell-quote = "0.7.2" subprocess = "0.2.9" toml = "0.8.20" yansi = "1.0.1" diff --git a/src/bootstrap.rs b/src/bootstrap.rs index 6640bfc..563111e 100644 --- a/src/bootstrap.rs +++ b/src/bootstrap.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use either::Either; use serde::Deserialize; +use shell_quote::{Bash, QuoteRefExt}; use subprocess::Exec; use crate::git::switch_branch; @@ -56,6 +57,7 @@ pub struct AskConfig { pub kind: AskKind, pub prompt: String, pub options: Option>, + pub order: Option, } #[derive(Debug, Deserialize)] @@ -135,9 +137,13 @@ pub fn build_script_vars(expose: Option>, vars: &[AskValue]) -> Stri return exp .iter() .map(|x| match x { - AskValue::Text(name, text) => format!("export {name}='{text}'"), + AskValue::Text(name, text) => { + format!("export {name}='{}'", || -> String { text.quoted(Bash) }()) + } AskValue::Number(name, num) => format!("export {name}={num}"), - AskValue::Selection(name, select) => format!("export {name}='{select}'"), + AskValue::Selection(name, select) => { + format!("export {name}='{}'", || -> String { select.quoted(Bash) }()) + } AskValue::Bool(name, b) => format!("export {name}='{}'", b.to_string()), }) .collect::>() @@ -198,7 +204,10 @@ pub fn bootstrap(base: &str, name: &str) { .unwrap(); let mut vars = Vec::new(); - for (var_name, def) in &config.ask { + let mut ask_vec: Vec<(&String, &AskConfig)> = config.ask.iter().collect(); + ask_vec.sort_by(|a, b| a.1.order.unwrap_or(0).cmp(&b.1.order.unwrap_or(0))); + + for (var_name, def) in ask_vec { match def.kind { AskKind::Text => { let a = inquire::prompt_text(&def.prompt).unwrap(); @@ -281,6 +290,7 @@ pub fn bootstrap(base: &str, name: &str) { } } + let _ = std::fs::remove_file("bootstrap.toml"); std::fs::remove_dir_all(std::path::Path::new(name).join(".git")).unwrap(); Exec::cmd("git") .arg("init") diff --git a/src/git.rs b/src/git.rs index 9486bac..2ccc2b0 100644 --- a/src/git.rs +++ b/src/git.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use subprocess::Exec; use yansi::{Color, Paint}; @@ -212,7 +214,8 @@ pub fn commit(all: bool, done: bool, forced: bool, msg: &str) { } } - let staged = git_staged(); + // Staging before pre commit actions + let staged_before = git_staged(); // Laguage specific pre-commit hooks for lang in get_languages() { @@ -224,6 +227,11 @@ pub fn commit(all: bool, done: bool, forced: bool, msg: &str) { } } + // Staging after pre commit actions + let staged_after = git_staged(); + + let staged = minus_union(staged_before, staged_after); + for file in staged { git_add(&file); } @@ -279,3 +287,24 @@ pub fn pull() { .wait() .unwrap(); } + +/// This function compares two vectors of strings returning the difference. +/// +/// # Example +/// ``` +/// use g::git::minus_union; +/// +/// let before = vec!["file1.txt".to_string(), "file2.txt".to_string(), "file3.txt".to_string()]; +/// let after = vec!["file2.txt".to_string(), "file3.txt".to_string(), "file4.txt".to_string()]; +/// +/// let modified_files = minus_union(before, after); +/// assert_eq!(modified_files, vec!["file4.txt"]); +/// ``` +pub fn minus_union(first: Vec, second: Vec) -> Vec { + let first_set: HashSet = first.into_iter().collect(); + let second_set: HashSet = second.into_iter().collect(); + + let modified_files: Vec = second_set.difference(&first_set).cloned().collect(); + + modified_files +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a5353f9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,102 @@ +pub mod args; +pub mod bootstrap; +pub mod git; +pub mod gitmoji; +pub mod precommit; + +use std::io::Read; +use subprocess::{Exec, Redirection}; +use yansi::{Color, Paint}; + +pub fn read_stdout(e: Exec) -> String { + let mut p = e.stdout(Redirection::Pipe).popen().unwrap(); + let mut str = String::new(); + p.stdout.as_mut().unwrap().read_to_string(&mut str).unwrap(); + str.trim().to_string() +} + +pub const TODO_REGEX: &str = r"( |#)(todo|unimplemented|refactor|wip|fix)(:|!|\n| :)"; +pub const NO_COMMIT_REGEX: &str = r"(NOCOMMIT|ENSURE: )"; + +pub fn no_commit_amount() -> u64 { + rg_matches(NO_COMMIT_REGEX) +} + +pub fn todos_amount() -> u64 { + rg_matches(TODO_REGEX) +} + +pub fn rg_matches(regex: &str) -> u64 { + let mut cmd = Exec::cmd("rg") + .arg("-i") + .arg("--json") + .arg("--multiline") + .arg(regex) + .stdout(Redirection::Pipe) + .popen() + .unwrap(); + cmd.wait().unwrap(); + let mut str = String::new(); + cmd.stdout + .as_mut() + .unwrap() + .read_to_string(&mut str) + .unwrap(); + let last_line = str.lines().last().unwrap(); + let val: serde_json::Value = serde_json::from_str(last_line).unwrap(); + let ret = (|| { + val.as_object() + .unwrap() + .get("data")? + .as_object()? + .get("stats")? + .as_object()? + .get("matches")? + .as_number() + .unwrap() + .as_i64() + })(); + ret.unwrap_or(0) as u64 +} + +pub fn show_rg(regex: &str) { + Exec::cmd("rg") + .arg("-i") + .arg("--multiline") + .arg(regex) + .popen() + .unwrap() + .wait() + .unwrap(); +} + +pub fn show_todos(count_only: bool) { + let amount = todos_amount(); + if amount == 0 { + println!("{}", "✨ No TODOs 🍃".paint(Color::Green).bold()); + } else { + let emoji = match amount { + 1..=5 => "🌱", + 6..=9 => "🙂", + 10..=19 => "😅", + 20..=49 => "🔥", + 50..=99 => "🤯", + 100..=149 => "💀", + 150.. => "⚰️", + _ => "💀", + }; + + println!( + "{emoji} {} TODOs found.", + if amount < 20 { + amount.paint(Color::Yellow).bold() + } else { + amount.paint(Color::Red).bold() + } + ); + + if !count_only { + show_rg(TODO_REGEX); + } + } +} diff --git a/src/main.rs b/src/main.rs index 69beaac..bcd19d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,111 +1,14 @@ -use bootstrap::bootstrap; -use git::{commit, create_new_branch, delete_branch, fetch, git_add, pull, push, revert_commits}; -use gitmoji::{CommitMessage, select_gitmoji}; -use std::io::Read; -use subprocess::{Exec, Redirection}; +use g::bootstrap::bootstrap; +use g::git::{ + commit, create_new_branch, delete_branch, fetch, git_add, pull, push, revert_commits, +}; +use g::gitmoji::{CommitMessage, select_gitmoji}; +use g::{NO_COMMIT_REGEX, no_commit_amount, show_rg, show_todos}; +use subprocess::Exec; use yansi::{Color, Paint}; -mod args; -mod bootstrap; -mod git; -mod gitmoji; -mod precommit; - -pub fn read_stdout(e: Exec) -> String { - let mut p = e.stdout(Redirection::Pipe).popen().unwrap(); - let mut str = String::new(); - p.stdout.as_mut().unwrap().read_to_string(&mut str).unwrap(); - str.trim().to_string() -} - -const TODO_REGEX: &str = r"( |#)(todo|unimplemented|refactor|wip|fix)(:|!|\n| :)"; -const NO_COMMIT_REGEX: &str = r"(NOCOMMIT|ENSURE: )"; - -pub fn no_commit_amount() -> u64 { - rg_matches(NO_COMMIT_REGEX) -} - -pub fn todos_amount() -> u64 { - rg_matches(TODO_REGEX) -} - -pub fn rg_matches(regex: &str) -> u64 { - let mut cmd = Exec::cmd("rg") - .arg("-i") - .arg("--json") - .arg("--multiline") - .arg(regex) - .stdout(Redirection::Pipe) - .popen() - .unwrap(); - cmd.wait().unwrap(); - let mut str = String::new(); - cmd.stdout - .as_mut() - .unwrap() - .read_to_string(&mut str) - .unwrap(); - let last_line = str.lines().last().unwrap(); - let val: serde_json::Value = serde_json::from_str(last_line).unwrap(); - let ret = (|| { - val.as_object() - .unwrap() - .get("data")? - .as_object()? - .get("stats")? - .as_object()? - .get("matches")? - .as_number() - .unwrap() - .as_i64() - })(); - ret.unwrap_or(0) as u64 -} - -pub fn show_rg(regex: &str) { - Exec::cmd("rg") - .arg("-i") - .arg("--multiline") - .arg(regex) - .popen() - .unwrap() - .wait() - .unwrap(); -} - -pub fn show_todos(count_only: bool) { - let amount = todos_amount(); - if amount == 0 { - println!("{}", "✨ No TODOs 🍃".paint(Color::Green).bold()); - } else { - let emoji = match amount { - 1..=5 => "🌱", - 6..=9 => "🙂", - 10..=19 => "😅", - 20..=49 => "🔥", - 50..=99 => "🤯", - 100..=149 => "💀", - 150.. => "⚰️", - _ => "💀", - }; - - println!( - "{emoji} {} TODOs found.", - if amount < 20 { - amount.paint(Color::Yellow).bold() - } else { - amount.paint(Color::Red).bold() - } - ); - - if !count_only { - show_rg(TODO_REGEX); - } - } -} - fn main() { - let args = args::get_args(); + let args = g::args::get_args(); match args.subcommand() { Some(("bootstrap", bs_args)) => {