diff --git a/.bumpversion.toml b/.bumpversion.toml index a8a01eb..30a07d0 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -1,22 +1,13 @@ -[bumpversion] -current_version = 0.1.4 -commit = True -tag = True -parse = (?P\d+)\.(?P\d+)\.(?P\d+)(-(?P[^.]*)\.(?P\d+))? -serialize = - {major}.{minor}.{patch}-{stage}.{devnum} - {major}.{minor}.{patch} +current_version = "0.1.4" +commit = true +tag = true +# parse = '(?P\d+)\.(?P\d+)\.(?P\d+)(-(?P[^.]*)\.(?P\d+))?' +# serialize = "{major}.{minor}.{patch}-{stage}.{devnum}" -[bumpversion:part:stage] -optional_value = stable -first_value = stable -values = - alpha - beta - stable +# example part: +[part.stage] +type = "string" +values = ["alpha", "beta", "stable"] -[bumpversion:part:devnum] - -[bumpversion:file:Cargo.toml] -search = version = "{current_version}" -replace = version = "{new_version}" +[file."Cargo.toml"] +format = 'version = "{version}"' diff --git a/Cargo.lock b/Cargo.lock index df22483..498f83f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -65,8 +65,19 @@ version = "0.1.4" dependencies = [ "clap", "regex", + "serde", + "serde_derive", + "toml", + "tracing", + "tracing-subscriber", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.5.1" @@ -113,12 +124,55 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.1" @@ -126,10 +180,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] -name = "proc-macro2" -version = "1.0.78" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -151,8 +233,17 @@ checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.5", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -163,15 +254,65 @@ checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.2", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "serde" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "strsim" version = "0.11.0" @@ -180,15 +321,120 @@ checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "syn" -version = "2.0.52" +version = "2.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -201,6 +447,34 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" @@ -266,3 +540,12 @@ name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index f25bc65..b8c6615 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,10 @@ keywords = ["cli", "parser"] categories = ["command-line-utilities"] repository = "https://github.com/wiseaidev/bump2version" documentation = "https://docs.rs/bump2version" -authors = ["Mahmoud Harmouch "] +authors = [ + "Mahmoud Harmouch ", + "TECHNOFAB ", +] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] @@ -17,6 +20,11 @@ crate-type = ["cdylib"] [dependencies] clap = { version = "4.5.1", features = ["derive"] } regex = "1.10.3" +serde = "1.0.216" +serde_derive = "1.0.216" +toml = "0.8.19" +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } [profile.release] codegen-units = 1 diff --git a/src/cli.rs b/src/cli.rs index 18a7cab..cf8a99c 100755 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,58 +1,8 @@ -use clap::builder::styling::{AnsiColor, Effects, Styles}; use clap::Parser; -fn styles() -> Styles { - Styles::styled() - .header(AnsiColor::Red.on_default() | Effects::BOLD) - .usage(AnsiColor::Red.on_default() | Effects::BOLD) - .literal(AnsiColor::Blue.on_default() | Effects::BOLD) - .error(AnsiColor::Red.on_default() | Effects::BOLD) - .placeholder(AnsiColor::Green.on_default()) -} - #[derive(Parser, Debug, Clone)] -#[command( - author = "Mahmoud Harmouch", - version, - name = "bump2version", - propagate_version = true, - styles = styles(), - help_template = r#"{before-help}{name} {version} -{about-with-newline} - -{usage-heading} {usage} - -{all-args}{after-help} - -AUTHORS: - {author} -"#, -about=r#" -🚀 Bump2version CLI -=================== - -Bump2version CLI is a command-line tool for managing version numbers in your projects. -Easily update version strings, create commits, and manage version control tags. - -FEATURES: - - Incremental Versioning: Bump major, minor, or patch versions with ease. - - Configurability: Use a configuration file or command-line options to customize behavior. - - Git Integration: Create commits and tags in your version control system. - -USAGE: - bump2version [OPTIONS] - -EXAMPLES: - Bump patch version: - bump2version --current-version 1.2.3 --bump patch - - Bump minor version and create a commit: - bump2version --current-version 1.2.3 --bump minor --commit - -For more information, visit: https://github.com/wiseaidev/bump2version -"# -)] -pub struct Cli { +#[command(version, name = "bump2version", propagate_version = true)] +pub(crate) struct Cli { /// Config file to read most of the variables from. #[arg( short = 'c', @@ -60,11 +10,7 @@ pub struct Cli { value_name = "FILE", default_value_t = String::from(".bumpversion.toml") )] - pub config_file: String, - - /// Version that needs to be updated. - #[arg(long = "current-version", value_name = "VERSION")] - pub current_version: Option, + pub(crate) config_file: String, /// Part of the version to be bumped. #[arg( @@ -72,50 +18,29 @@ pub struct Cli { value_name = "PART", default_value_t = String::from("patch") )] - pub bump: String, - - /// Regex parsing the version string. - #[arg( - long = "parse", - value_name = "REGEX", - default_value_t = String::from(r"(?P\d+)\.(?P\d+)\.(?P\d+)") - )] - pub parse: String, - - /// How to format what is parsed back to a version. - #[arg( - long = "serialize", - value_name = "FORMAT", - default_value_t = String::from("{major}.{minor}.{patch}") - )] - pub serialize: String, + pub(crate) bump: String, /// Don't write any files, just pretend. #[arg(short = 'n', long = "dry-run", default_value_t = false)] - pub dry_run: bool, + pub(crate) dry_run: bool, /// New version that should be in the files. #[arg(long = "new-version", value_name = "VERSION")] - pub new_version: Option, + pub(crate) new_version: Option, /// Create a commit in version control. - #[arg(long = "commit", default_value_t = true)] - pub commit: bool, + #[arg(long = "commit")] + pub(crate) commit: Option, /// Create a tag in version control. #[arg(long = "tag")] - pub tag: bool, + pub(crate) tag: Option, + + /// Whether to fail on a dirty git repository + #[arg(long = "fail-on-dirty", default_value_t = false)] + pub(crate) fail_on_dirty: bool, /// Commit message. - #[arg( - short = 'm', - long = "message", - value_name = "COMMIT_MSG", - default_value_t = String::from("Bump version: {current_version} → {new_version}") - )] - pub message: String, - - /// Files to change. - #[arg(value_name = "file")] - pub files: Vec, + #[arg(short = 'm', long = "message", value_name = "COMMIT_MSG")] + pub(crate) message: Option, } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..3da1183 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; + +use serde_derive::Deserialize; + +#[derive(Debug, Clone, Deserialize)] +pub(crate) struct Config { + pub(crate) current_version: String, + pub(crate) message: Option, + pub(crate) commit: bool, + pub(crate) tag: bool, + #[serde(default = "default_parse")] + pub(crate) parse: String, + #[serde(default = "default_serialize")] + pub(crate) serialize: String, + + pub(crate) part: HashMap, + pub(crate) file: HashMap, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "lowercase")] +pub(crate) enum PartType { + String, + #[serde(other)] + Number, +} + +#[derive(Debug, Clone, Deserialize)] +pub(crate) struct Part { + /// Type of the part (number or string) + pub(crate) r#type: PartType, + /// Valid values for the part (only used when type is string) + pub(crate) values: Option>, +} + +#[derive(Debug, Clone, Deserialize)] +pub(crate) struct File { + /// Format to replace, eg. `current_version = "{version}"` + pub(crate) format: String, +} + +fn default_parse() -> String { + String::from(r"(?P\d+)\.(?P\d+)\.(?P\d+)") +} + +fn default_serialize() -> String { + String::from("{major}.{minor}.{patch}") +} diff --git a/src/lib.rs b/src/lib.rs index 7f21eee..022eb5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] -pub mod cli; -pub mod utils; +pub(crate) mod cli; +pub(crate) mod config; +pub(crate) mod utils; diff --git a/src/main.rs b/src/main.rs index 31fc033..ace6f87 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,47 +1,63 @@ use self::cli::Cli; +use crate::config::Config; 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; +use std::process::{exit, Command}; +use tracing::{error, info, level_filters::LevelFilter, warn}; +use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; mod cli; +mod config; mod utils; fn main() -> Result<(), Box> { + tracing_subscriber::registry() + .with(fmt::layer()) + .with( + EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(), + ) + .init(); + let args = Cli::parse(); let config_file = args.config_file.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("failed to get current version from config")?; - let current_version = args - .current_version - .clone() - .unwrap_or(config_version) - .clone(); - - let attempted_new_version = if let Some(version) = args.new_version { - Some(version) - } else { - attempt_version_bump(args.clone()) + let contents = match fs::read_to_string(&config_file) { + Ok(c) => c, + Err(err) => { + error!(?err, config_file, "Could not read config"); + exit(1); + } }; + let config: Config = match toml::from_str(&contents) { + Ok(d) => d, + Err(err) => { + error!(err = err.to_string(), config_file, "Unable to load config"); + exit(1); + } + }; + + let current_version = config.current_version.clone(); + + let attempted_new_version = args + .new_version + .clone() + .or(attempt_version_bump(args.clone(), config.clone())); if attempted_new_version.is_some() { let new_version = attempted_new_version.clone().unwrap(); + info!(current_version, new_version, "Bumping version"); let dry_run = args.dry_run; - let commit = args.commit; - let tag = args.tag; - let message = args.message; + let commit = args.commit.unwrap_or(config.commit); + let tag = args.tag.unwrap_or(config.tag); + let message = args + .message + .or(config.message) + .unwrap_or("Bump version: {current_version} → {new_version}".to_string()); - 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 - }; + let files: Vec<&String> = config.file.keys().collect(); // Check if Git working directory is clean if fs::metadata(".git").is_ok() { @@ -57,19 +73,29 @@ fn main() -> Result<(), Box> { .collect(); if !git_lines.is_empty() { - panic!("Git working directory not clean:\n{}", git_lines.join("\n")); + warn!("Git working directory not clean:\n{}", git_lines.join("\n")); + if args.fail_on_dirty { + exit(1); + } } } // Update version in specified files - for path in &files { + info!(amount = &files.len(), "Updating version in files"); + for path in files.clone() { let content = fs::read_to_string(path)?; + let format = &config.file.get(path).unwrap().format; - if !content.contains(¤t_version) { - panic!("Did not find string {} in file {}", current_version, path); + let old_line = format.replace("{version}", ¤t_version); + if !content.contains(&old_line) { + warn!( + current_version, + path, "Did not find current version in file" + ); } - let updated_content = content.replace(¤t_version, &new_version); + let new_line = format.replace("{version}", &new_version); + let updated_content = content.replace(&old_line, &new_line); if !dry_run { fs::write(path, updated_content)?; @@ -78,18 +104,20 @@ fn main() -> Result<(), Box> { let mut commit_files = files.clone(); - // Update config file if applicable - if fs::metadata(config_file.clone()).is_ok() { + // Update config file if applicable & writable + let metadata = fs::metadata(config_file.clone()); + if metadata.is_ok() && !metadata.unwrap().permissions().readonly() { + info!("Updating version in config file"); let mut config_content = fs::read_to_string(config_file.clone())?; 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 { fs::write(config_file.clone(), config_content)?; - commit_files.push(config_file); + commit_files.push(&config_file); } } if commit { @@ -100,11 +128,11 @@ fn main() -> Result<(), Box> { Ok(output) => { if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - eprintln!("Error during git add:\n{}", stderr); + error!(?stderr, "Error during git add"); } } Err(err) => { - eprintln!("Failed to execute git add: {}", err); + error!(?err, "Failed to execute git add"); } } } @@ -126,24 +154,22 @@ fn main() -> Result<(), Box> { match commit_output { Ok(commit_output) => { if commit_output.status.success() { - println!("Git commit successful"); + info!("Git commit successful"); } else { - eprintln!( - "Error during git commit:\n{}", - String::from_utf8_lossy(&commit_output.stderr) - ); + let stderr = String::from_utf8_lossy(&commit_output.stderr); + error!(?stderr, "Error during git commit",); } } Err(err) => { - eprintln!("Failed to execute git commit: {}", err); + error!(?err, "Failed to execute git commit"); } } } else { - println!("No changes to commit. Working tree clean."); + warn!("No changes to commit. Working tree clean."); } } Err(err) => { - eprintln!("Failed to execute git diff: {}", err); + error!(?err, "Failed to execute git diff"); } } @@ -155,7 +181,7 @@ fn main() -> Result<(), Box> { } } } else { - eprintln!("No files specified"); + error!("No new version passed and generating new version failed"); } Ok(()) diff --git a/src/utils.rs b/src/utils.rs index fea255c..7e31e12 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,54 +1,22 @@ use crate::cli::Cli; +use crate::config::{Config, PartType}; use regex::Regex; +use std::cmp::min; use std::collections::HashMap; -use std::collections::HashSet; -use std::fs; +use std::ops::Index; +use tracing::{error, trace}; -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.clone(); +pub fn attempt_version_bump(args: Cli, config: Config) -> Option { + let parse_regex = config.parse; let regex = match Regex::new(&parse_regex) { Ok(r) => r, - Err(_) => { - eprintln!("--patch '{}' is not a valid regex", args.parse.clone()); + Err(err) => { + error!(?err, parse_regex, "Invalid 'parse' regex"); return None; } }; - 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 current_version = config.current_version; let mut parsed: HashMap = HashMap::new(); if let Some(captures) = regex.captures(¤t_version) { @@ -59,41 +27,83 @@ pub fn attempt_version_bump(args: Cli) -> Option { } } - let order: Vec<&str> = args + let order: Vec<&str> = config .serialize .match_indices('{') - .map(|(i, _)| args.serialize[i + 1..].split('}').next().unwrap().trim()) + .map(|(i, _)| config.serialize[i + 1..].split('}').next().unwrap().trim()) .collect(); + trace!(?order, "detected version parts"); + let mut bumped = false; - for label in order { + let part_configs = config.part.clone(); + + for label in order.clone() { if let Some(part) = parsed.get_mut(label) { + let part_cfg = part_configs.get(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; + match part_cfg + .map(|cfg| cfg.r#type.clone()) + .unwrap_or(PartType::Number) + { + PartType::String => { + let values = part_cfg + .unwrap() + .values + .clone() + .expect("part values do not exist for string type"); + let old_index: usize = values + .iter() + .position(|val| val == part) + .expect("part value does not exist"); + let new_index: usize = min(old_index + 1, values.len() - 1); + *part = values.index(new_index).to_string(); + bumped = true; + } + PartType::Number => { + if let Ok(old_value) = part.parse::() { + *part = (old_value + 1).to_string(); + bumped = true; + } else { + error!(part, "Failed to parse as u64"); + return None; + } + } } } else if bumped { - *part = "0".to_string(); + match part_cfg + .map(|cfg| cfg.r#type.clone()) + .unwrap_or(PartType::Number) + { + PartType::Number => *part = "0".to_string(), + PartType::String => { + let values = part_cfg + .unwrap() + .values + .clone() + .expect("part values do not exist for string type"); + *part = values.index(0).to_string(); + } + } } + } else { + trace!(label, "part not found"); } } if bumped { - 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()) - ); - let version = args - .serialize - .replace("{major}.{minor}.{patch}", &new_version); - Some(version) + let mut new_version = config.serialize.clone(); + for part in order { + trace!(new_version, part, "building new version"); + new_version = new_version.replace( + &format!("{{{}}}", part), + parsed.get(part).expect("unexpected part in version found"), + ); + } + trace!(new_version, "created new version"); + Some(new_version) } else { None }