♻️ refactor + bootstrap ask order + shell_quote
All checks were successful
ci/woodpecker/push/build Pipeline was successful

This commit is contained in:
JMARyA 2025-04-08 08:38:04 +02:00
parent 2e748abffc
commit f0fb42b6f0
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
6 changed files with 220 additions and 148 deletions

103
Cargo.lock generated
View file

@ -66,9 +66,20 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.8.0" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "byteorder" name = "byteorder"
@ -84,18 +95,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.26" version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
] ]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.26" version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -142,9 +153,9 @@ dependencies = [
[[package]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.17" version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
[[package]] [[package]]
name = "either" name = "either"
@ -181,13 +192,14 @@ dependencies = [
[[package]] [[package]]
name = "g" name = "g"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"clap", "clap",
"either", "either",
"inquire", "inquire",
"serde", "serde",
"serde_json", "serde_json",
"shell-quote",
"subprocess", "subprocess",
"toml", "toml",
"yansi", "yansi",
@ -215,7 +227,7 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.0",
"crossterm", "crossterm",
"dyn-clone", "dyn-clone",
"fuzzy-matcher", "fuzzy-matcher",
@ -234,15 +246,15 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.14" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.169" version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -256,9 +268,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.25" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "memchr" name = "memchr"
@ -289,9 +301,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.20.2" version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
@ -318,36 +330,42 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.93" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.38" version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.8" version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.0",
] ]
[[package]] [[package]]
name = "ryu" name = "regex-automata"
version = "1.0.18" version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "scopeguard" name = "scopeguard"
@ -377,9 +395,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.137" version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -396,6 +414,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "shell-quote"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb502615975ae2365825521fa1529ca7648fd03ce0b0746604e0683856ecd7e4"
dependencies = [
"bstr",
]
[[package]] [[package]]
name = "signal-hook" name = "signal-hook"
version = "0.3.17" version = "0.3.17"
@ -428,9 +455,9 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.13.2" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]] [[package]]
name = "strsim" name = "strsim"
@ -450,9 +477,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.96" version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -505,9 +532,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.14" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
@ -696,9 +723,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.4" version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View file

@ -1,6 +1,6 @@
[package] [package]
name = "g" name = "g"
version = "0.1.0" version = "0.1.1"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
@ -9,6 +9,7 @@ either = { version = "1.15.0", features = ["serde"] }
inquire = "0.7.5" inquire = "0.7.5"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.137" serde_json = "1.0.137"
shell-quote = "0.7.2"
subprocess = "0.2.9" subprocess = "0.2.9"
toml = "0.8.20" toml = "0.8.20"
yansi = "1.0.1" yansi = "1.0.1"

View file

@ -2,6 +2,7 @@ use std::collections::HashMap;
use either::Either; use either::Either;
use serde::Deserialize; use serde::Deserialize;
use shell_quote::{Bash, QuoteRefExt};
use subprocess::Exec; use subprocess::Exec;
use crate::git::switch_branch; use crate::git::switch_branch;
@ -56,6 +57,7 @@ pub struct AskConfig {
pub kind: AskKind, pub kind: AskKind,
pub prompt: String, pub prompt: String,
pub options: Option<Vec<String>>, pub options: Option<Vec<String>>,
pub order: Option<u64>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -135,9 +137,13 @@ pub fn build_script_vars(expose: Option<Vec<String>>, vars: &[AskValue]) -> Stri
return exp return exp
.iter() .iter()
.map(|x| match x { .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::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()), AskValue::Bool(name, b) => format!("export {name}='{}'", b.to_string()),
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -198,7 +204,10 @@ pub fn bootstrap(base: &str, name: &str) {
.unwrap(); .unwrap();
let mut vars = Vec::new(); 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 { match def.kind {
AskKind::Text => { AskKind::Text => {
let a = inquire::prompt_text(&def.prompt).unwrap(); 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(); std::fs::remove_dir_all(std::path::Path::new(name).join(".git")).unwrap();
Exec::cmd("git") Exec::cmd("git")
.arg("init") .arg("init")

View file

@ -1,3 +1,5 @@
use std::collections::HashSet;
use subprocess::Exec; use subprocess::Exec;
use yansi::{Color, Paint}; 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 // Laguage specific pre-commit hooks
for lang in get_languages() { 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 { for file in staged {
git_add(&file); git_add(&file);
} }
@ -279,3 +287,24 @@ pub fn pull() {
.wait() .wait()
.unwrap(); .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<String>, second: Vec<String>) -> Vec<String> {
let first_set: HashSet<String> = first.into_iter().collect();
let second_set: HashSet<String> = second.into_iter().collect();
let modified_files: Vec<String> = second_set.difference(&first_set).cloned().collect();
modified_files
}

102
src/lib.rs Normal file
View file

@ -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);
}
}
}

View file

@ -1,111 +1,14 @@
use bootstrap::bootstrap; use g::bootstrap::bootstrap;
use git::{commit, create_new_branch, delete_branch, fetch, git_add, pull, push, revert_commits}; use g::git::{
use gitmoji::{CommitMessage, select_gitmoji}; commit, create_new_branch, delete_branch, fetch, git_add, pull, push, revert_commits,
use std::io::Read; };
use subprocess::{Exec, Redirection}; 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}; 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() { fn main() {
let args = args::get_args(); let args = g::args::get_args();
match args.subcommand() { match args.subcommand() {
Some(("bootstrap", bs_args)) => { Some(("bootstrap", bs_args)) => {