feat: rewrite huge parts

- real config parsing
- actually working parts, with support for string parts
- handle readonly config (preparation for use with Nix)
- cleanup cli (remove colors, fancy stuff etc., keep it minimal)
- switch to tracing for logging
This commit is contained in:
TECHNOFAB 2024-12-27 13:46:25 +01:00
parent 5b895b7939
commit 5e120a6ab8
No known key found for this signature in database
GPG key ID: D06FBA11BA6FF836
8 changed files with 522 additions and 230 deletions

View file

@ -1,22 +1,13 @@
[bumpversion]
current_version = 0.1.4
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-(?P<stage>[^.]*)\.(?P<devnum>\d+))?
serialize =
{major}.{minor}.{patch}-{stage}.{devnum}
{major}.{minor}.{patch}
current_version = "0.1.4"
commit = true
tag = true
# parse = '(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-(?P<stage>[^.]*)\.(?P<devnum>\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}"'

301
Cargo.lock generated
View file

@ -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",
]

View file

@ -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 <oss@wiseai.dev>"]
authors = [
"Mahmoud Harmouch <oss@wiseai.dev>",
"TECHNOFAB <admin@technofab.de>",
]
# 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

View file

@ -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<String>,
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<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\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<String>,
pub(crate) new_version: Option<String>,
/// Create a commit in version control.
#[arg(long = "commit", default_value_t = true)]
pub commit: bool,
#[arg(long = "commit")]
pub(crate) commit: Option<bool>,
/// Create a tag in version control.
#[arg(long = "tag")]
pub tag: bool,
pub(crate) tag: Option<bool>,
/// 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<String>,
#[arg(short = 'm', long = "message", value_name = "COMMIT_MSG")]
pub(crate) message: Option<String>,
}

48
src/config.rs Normal file
View file

@ -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<String>,
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<String, Part>,
pub(crate) file: HashMap<String, File>,
}
#[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<Vec<String>>,
}
#[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<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)")
}
fn default_serialize() -> String {
String::from("{major}.{minor}.{patch}")
}

View file

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

View file

@ -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<dyn std::error::Error>> {
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<String> = if args.files.is_empty() {
let config_files: HashSet<String> = 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<dyn std::error::Error>> {
.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(&current_version) {
panic!("Did not find string {} in file {}", current_version, path);
let old_line = format.replace("{version}", &current_version);
if !content.contains(&old_line) {
warn!(
current_version,
path, "Did not find current version in file"
);
}
let updated_content = content.replace(&current_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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
}
}
} else {
eprintln!("No files specified");
error!("No new version passed and generating new version failed");
}
Ok(())

View file

@ -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<String> {
let current_version_regex =
Regex::new(r#"\[bumpversion\]\s*current_version\s*=\s*(?P<version>\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<HashSet<String>, 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<String> {
let parse_regex = args.parse.clone();
pub fn attempt_version_bump(args: Cli, config: Config) -> Option<String> {
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<String, String> = HashMap::new();
if let Some(captures) = regex.captures(&current_version) {
@ -59,41 +27,83 @@ pub fn attempt_version_bump(args: Cli) -> Option<String> {
}
}
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::<u64>() {
*part = (new_value + 1).to_string();
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::<u64>() {
*part = (old_value + 1).to_string();
bumped = true;
} else {
eprintln!("Failed to parse '{}' as u64", part);
error!(part, "Failed to parse as u64");
return None;
}
} else if bumped {
*part = "0".to_string();
}
}
} else if bumped {
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 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"),
);
let version = args
.serialize
.replace("{major}.{minor}.{patch}", &new_version);
Some(version)
}
trace!(new_version, "created new version");
Some(new_version)
} else {
None
}