use crate::config::{BumpConfig, FileConfig};
use crate::error::BumpError;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use regex::Regex;
#[cfg(feature = "std")]
fn cached_regex(pattern: &str) -> Result<Arc<Regex>, BumpError> {
use std::collections::HashMap;
use std::sync::{OnceLock, RwLock};
static CACHE: OnceLock<RwLock<HashMap<String, Arc<Regex>>>> = OnceLock::new();
let cache = CACHE.get_or_init(|| RwLock::new(HashMap::new()));
{
let guard = cache.read().unwrap_or_else(|p| p.into_inner());
if let Some(re) = guard.get(pattern) {
return Ok(Arc::clone(re));
}
}
let re = Regex::new(pattern).map_err(|e| BumpError::InvalidRegex(pattern.to_string(), e))?;
let re = Arc::new(re);
cache
.write()
.unwrap_or_else(|p| p.into_inner())
.insert(pattern.to_string(), Arc::clone(&re));
Ok(re)
}
#[cfg(not(feature = "std"))]
fn cached_regex(pattern: &str) -> Result<Arc<Regex>, BumpError> {
Regex::new(pattern)
.map_err(|e| BumpError::InvalidRegex(pattern.to_string(), e))
.map(Arc::new)
}
fn render_pattern(template: &str, current_version: &str, new_version: &str) -> String {
template
.replace("{current_version}", current_version)
.replace("{new_version}", new_version)
}
fn escape_literal(text: &str) -> String {
regex::escape(text)
}
pub fn apply_file_change(
content: &str,
file_cfg: &FileConfig,
cfg: &BumpConfig,
current_version: &str,
new_version: &str,
) -> Result<String, BumpError> {
let search_template = file_cfg.search.as_deref().unwrap_or(&cfg.search);
let replace_template = file_cfg.replace.as_deref().unwrap_or(&cfg.replace);
let rendered_search = render_pattern(search_template, current_version, new_version);
let rendered_replace = render_pattern(replace_template, current_version, new_version);
let is_multiline = rendered_search.contains('\n');
let pattern = if is_multiline {
let escaped = escape_literal(&rendered_search);
format!("(?ms){}", escaped)
} else {
escape_literal(&rendered_search)
};
let re = cached_regex(&pattern)?;
if !re.is_match(content) {
if !file_cfg.ignore_missing_version {
return Err(BumpError::VersionNotFound(
rendered_search,
file_cfg.path.clone(),
));
}
return Ok(content.to_string());
}
Ok(re
.replace_all(content, regex::NoExpand(&rendered_replace))
.into_owned())
}
pub fn apply_config_version_update(
content: &str,
current_version: &str,
new_version: &str,
) -> String {
let candidates = [
(
format!("current_version = \"{}\"", current_version),
format!("current_version = \"{}\"", new_version),
),
(
format!("current_version = '{}'", current_version),
format!("current_version = '{}'", new_version),
),
(
format!("current_version = {}", current_version),
format!("current_version = {}", new_version),
),
];
for (old, new) in &candidates {
if content.contains(old.as_str()) {
return content.replacen(old.as_str(), new.as_str(), 1);
}
}
content.to_string()
}