diff --git a/.bumpversion.cfg b/.bumpversion.toml similarity index 100% rename from .bumpversion.cfg rename to .bumpversion.toml diff --git a/Cargo.lock b/Cargo.lock index 2ff24e9..53bc586 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.12" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -61,7 +61,7 @@ dependencies = [ [[package]] name = "bump2version" -version = "0.1.0" +version = "0.1.1" dependencies = [ "clap", "regex", @@ -180,9 +180,9 @@ checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "syn" -version = "2.0.49" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -227,42 +227,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/src/cli.rs b/src/cli.rs index a46d5df..18a7cab 100755 --- a/src/cli.rs +++ b/src/cli.rs @@ -64,7 +64,7 @@ pub struct Cli { /// Version that needs to be updated. #[arg(long = "current-version", value_name = "VERSION")] - pub current_version: String, + pub current_version: Option, /// Part of the version to be bumped. #[arg( @@ -91,12 +91,12 @@ pub struct Cli { pub serialize: String, /// Don't write any files, just pretend. - #[arg(short = 'n', long = "dry-run")] + #[arg(short = 'n', long = "dry-run", default_value_t = false)] pub dry_run: bool, /// New version that should be in the files. #[arg(long = "new-version", value_name = "VERSION")] - pub new_version: String, + pub new_version: Option, /// Create a commit in version control. #[arg(long = "commit", default_value_t = true)] diff --git a/src/main.rs b/src/main.rs index 58e5c4c..8c40269 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ use self::cli::Cli; use crate::utils::attempt_version_bump; +use crate::utils::get_current_version_from_config; +use crate::utils::read_files_from_config; use clap::Parser; +use std::collections::HashSet; use std::fs; use std::process::Command; @@ -8,23 +11,32 @@ mod cli; mod utils; fn main() -> Result<(), Box> { - // Parse command-line arguments let args = Cli::parse(); let config_file = args.config_file.clone(); - let current_version = args.current_version.clone(); + let config_content = fs::read_to_string(args.config_file.clone()).unwrap(); + let config_version = get_current_version_from_config(&config_content).ok_or("")?; + let current_version = args + .current_version + .clone() + .unwrap_or(config_version) + .clone(); let attempted_new_version = attempt_version_bump(args.clone()); - if !args.new_version.is_empty() && attempted_new_version.is_some() { - // TODO: fix let new_version = attempted_new_version.clone().unwrap(); - let new_version = args.new_version.clone(); + if attempted_new_version.is_some() { + let new_version = attempted_new_version.clone().unwrap(); let dry_run = args.dry_run; let commit = args.commit; let tag = args.tag; let message = args.message; - let files: Vec = args.files; + let files: Vec = if args.files.is_empty() { + let config_files: HashSet = read_files_from_config(&args.config_file)?; + config_files.into_iter().collect() + } else { + args.files + }; // Check if Git working directory is clean if fs::metadata(".git").is_ok() { @@ -66,12 +78,8 @@ fn main() -> Result<(), Box> { let mut config_content = fs::read_to_string(config_file.clone())?; config_content = config_content.replace( - &format!("new_version={}", attempted_new_version.unwrap()), - "", - ); - config_content = config_content.replace( - &format!("current_version={}", current_version), - &format!("current_version={}", new_version), + &format!("current_version = {}", current_version), + &format!("current_version = {}", new_version), ); if !dry_run { @@ -79,22 +87,62 @@ fn main() -> Result<(), Box> { commit_files.push(config_file); } } - - // Git commit and tag if commit { for path in &commit_files { - Command::new("git").arg("add").arg(path).output()?; - } + let git_add_output = Command::new("git").arg("add").arg(path).output(); - Command::new("git") - .arg("commit") - .arg("-m") - .arg( - message - .replace("{current_version}", ¤t_version) - .replace("{new_version}", &new_version), - ) - .output()?; + match git_add_output { + Ok(output) => { + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + eprintln!("Error during git add:\n{}", stderr); + } + } + Err(err) => { + eprintln!("Failed to execute git add: {}", err); + } + } + } + let git_diff_output = Command::new("git").arg("diff").output(); + + match git_diff_output { + Ok(output) => { + if !output.stdout.is_empty() { + // There are changes, proceed with git commit + let commit_output = Command::new("git") + .arg("commit") + .arg("-m") + .arg( + message + .replace("{current_version}", ¤t_version) + .replace("{new_version}", &new_version), + ) + .output(); + + match commit_output { + Ok(commit_output) => { + if commit_output.status.success() { + println!("Git commit successful"); + } else { + eprintln!( + "Error during git commit:\n{}", + String::from_utf8_lossy(&commit_output.stderr) + ); + } + } + Err(err) => { + eprintln!("Failed to execute git commit: {}", err); + } + } + } else { + // No changes to commit + println!("No changes to commit. Working tree clean."); + } + } + Err(err) => { + eprintln!("Failed to execute git diff: {}", err); + } + } if tag { Command::new("git") @@ -104,7 +152,7 @@ fn main() -> Result<(), Box> { } } } else { - println!("No files specified"); + eprintln!("No files specified"); } Ok(()) diff --git a/src/utils.rs b/src/utils.rs index b8f327c..d64ea51 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,49 +1,101 @@ use crate::cli::Cli; use regex::Regex; use std::collections::HashMap; +use std::collections::HashSet; +use std::fs; + +pub fn get_current_version_from_config(config_content: &str) -> Option { + let current_version_regex = + Regex::new(r#"\[bumpversion\]\s*current_version\s*=\s*(?P\d+(\.\d+){0,2})\s*"#) + .unwrap(); + + if let Some(captures) = current_version_regex.captures(config_content) { + if let Some(version) = captures.name("version") { + return Some(version.as_str().to_string()); + } + } + + None +} + +// Function to read files from the configuration file +pub fn read_files_from_config(config_file: &str) -> Result, std::io::Error> { + let config_content = fs::read_to_string(config_file)?; + let mut config_files = HashSet::new(); + + for line in config_content.lines() { + if let Some(file_section) = line.strip_prefix("[bumpversion:file:") { + if let Some(file_name) = file_section.split(']').next() { + config_files.insert(file_name.trim().to_string()); + } + } + } + + Ok(config_files) +} pub fn attempt_version_bump(args: Cli) -> Option { - let parse_regex = args.parse; - let regex = Regex::new(&parse_regex).ok()?; + let parse_regex = args.parse.clone(); + let regex = match Regex::new(&parse_regex) { + Ok(r) => r, + Err(_) => { + eprintln!("--patch '{}' is not a valid regex", args.parse.clone()); + return None; + } + }; - let current_version = args.current_version; - let match_result = regex.captures_iter(¤t_version); + let config_content = fs::read_to_string(args.config_file.clone()).unwrap(); + let current_version = get_current_version_from_config(&config_content).unwrap_or_else(|| { + panic!("Failed to extract current version from config file"); + }); + // let current_version = args.current_version.unwrap_or("".to_string()); + let mut parsed: HashMap = HashMap::new(); - let mut parsed: HashMap<&str, &str> = HashMap::new(); - - for caps in match_result { - if let (Some(name), Some(value)) = (caps.name("name"), caps.name("value")) { - parsed.insert(name.as_str(), value.as_str()); + if let Some(captures) = regex.captures(¤t_version) { + for name in regex.capture_names() { + if let Some(name) = name { + if let Some(capture) = captures.name(name) { + parsed.insert(name.to_string(), capture.as_str().to_string()); + } + } } } let order: Vec<&str> = args .serialize .match_indices('{') - .map(|(i, _)| args.serialize[i + 1..].split('}').next().unwrap()) - .map(|s| s.trim()) + .map(|(i, _)| args.serialize[i + 1..].split('}').next().unwrap().trim()) .collect(); - let mut bumped = true; + let mut bumped = false; + for label in order { - if label == args.bump { - if let Some(_part) = parsed.get_mut(label) { - // TODO: fix - // let new_value = part.parse::().unwrap() + 1; - // *part = &new_value.clone().to_string(); - bumped = true; + if let Some(part) = parsed.get_mut(label) { + if label == args.bump { + if let Ok(new_value) = part.parse::() { + *part = (new_value + 1).to_string(); + bumped = true; + } else { + eprintln!("Failed to parse '{}' as u64", part); + return None; + } + } else if bumped { + *part = "0".to_string(); } - } else if bumped { - parsed.insert(label, "0"); } } if bumped { - let new_version = args.serialize.replace( - |c| c == '{' || c == '}', - parsed.get(&"{").unwrap_or(&"").to_string().as_str(), // TODO: fix c + let new_version = format!( + "{}.{}.{}", + parsed.get("major").unwrap_or(&"0".to_string()), + parsed.get("minor").unwrap_or(&"0".to_string()), + parsed.get("patch").unwrap_or(&"0".to_string()) ); - Some(new_version) + let version = args + .serialize + .replace("{major}.{minor}.{patch}", &new_version); + Some(version) } else { None }