diff --git a/src/app.rs b/src/app.rs index 4f12e25..5260a5f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,14 +4,15 @@ use bomper::{ config::Config, error::{Error, Result}, replacers::{ - cargo::CargoReplacer, search::SearchReplacer, simple::SimpleReplacer, Replacer, - VersionReplacement, + cargo::CargoReplacer, file::FileReplacer, search::SearchReplacer, simple::SimpleReplacer, + Replacer, VersionReplacement, }, versioning::{ determine_increment, get_commits_since_tag, get_latest_tag, increment_version, VersionIncrement, }, }; +use std::io::Write; pub struct App { pub args: BaseArgs, @@ -55,66 +56,126 @@ impl App { _ => unreachable!(), }; let new_version = increment_version(tag.version.clone(), increment); - println!("New version: {}", new_version); let changelog_entry = generate_changelog_entry(&commits, &new_version); - println!("{}", changelog_entry); - todo!() + + let replacement = VersionReplacement { + old_version: tag.version.to_string(), + new_version: new_version.to_string(), + }; + let mut file_changes = determine_changes(&self.config, &replacement)?; + file_changes.push(apply_changelog(changelog_entry)?); + apply_changes(file_changes, &self.args)?; + + Ok(()) } pub fn raw_bump(&self, opts: &RawBump) -> Result<()> { - // self.config.file.clone().par_drain().for_each(|path| { - let versions = VersionReplacement { + let replacement = VersionReplacement { old_version: opts.old_version.clone(), new_version: opts.new_version.clone(), }; - let mut files_to_replace = Vec::new(); - - let by_file = &self.config.by_file; - if let Some(by_file) = by_file { - for (path, config) in by_file { - let mut replacers = match &config.search_value { - Some(value) => SearchReplacer::new( - path.clone(), - &opts.old_version, - value, - &opts.new_version, - )? - .determine_replacements()?, - None => { - SimpleReplacer::new(path.clone(), &opts.old_version, &opts.new_version)? - .determine_replacements()? - } - }; - - // append new replacers to the list - if let Some(replacers) = &mut replacers { - files_to_replace.append(replacers); - } - } + let file_changes = determine_changes(&self.config, &replacement)?; + apply_changes(file_changes, &self.args)?; + + Ok(()) + } +} + +/// Persist file changes to the filesystem. +/// This function is responsible for respecting the `dry_run` flag, so it will only persist changes +/// if the flag is not set. +fn apply_changes(changes: Vec, args: &BaseArgs) -> Result<()> { + if args.dry_run { + println!("Dry run, not persisting changes"); + for replacer in changes { + println!("Would have replaced: {}", replacer.path.display()); } - let cargo_lock = &self.config.cargo; - if let Some(cargo_lock) = cargo_lock { - let replacer = CargoReplacer::new(versions, cargo_lock.clone())?; - let mut files = replacer.determine_replacements()?; - if let Some(files) = &mut files { - files_to_replace.append(files); - } + return Ok(()); + } else { + for replacer in changes { + replacer.persist()?; } + } - if self.args.dry_run { - println!("Dry run, not persisting changes"); - for replacer in files_to_replace { - println!("Would have replaced: {}", replacer.path.display()); - } + Ok(()) +} + +/// Determine the changes to make to the repository to update the version. +fn determine_changes( + config: &Config, + replacement: &VersionReplacement, +) -> Result> { + let mut files_to_replace = Vec::new(); - return Ok(()); - } else { - for replacer in files_to_replace { - replacer.persist()?; + let by_file = &config.by_file; + if let Some(by_file) = by_file { + for (path, config) in by_file { + let mut replacers = match &config.search_value { + Some(value) => SearchReplacer::new( + path.clone(), + &replacement.old_version, + value, + &replacement.new_version, + )? + .determine_replacements()?, + None => SimpleReplacer::new( + path.clone(), + &replacement.old_version, + &replacement.new_version, + )? + .determine_replacements()?, + }; + + // append new replacers to the list + if let Some(replacers) = &mut replacers { + files_to_replace.append(replacers); } } + } - Ok(()) + let cargo_lock = &config.cargo; + if let Some(cargo_lock) = cargo_lock { + let replacer = CargoReplacer::new(replacement.clone(), cargo_lock.clone())?; + let mut files = replacer.determine_replacements()?; + if let Some(files) = &mut files { + files_to_replace.append(files); + } } + + Ok(files_to_replace) +} + +/// Stitch together the existing changelog with the new one. +/// This is done using `- - -` as a marker character. +/// The new changelog is composed of the changelog header (everything from the start to the first +/// marker`, the new entry (with a marker on top), and the remaining part of the previous changelog +fn create_changelog(path: &std::path::Path, contents: &str) -> Result { + const MARKER: &str = "- - -"; + + match std::path::Path::try_exists(path) { + Ok(true) => { + let original_changelog = std::fs::read_to_string(path)?; + let start = original_changelog + .find(MARKER) + .ok_or(Error::ChangelogMarker)?; + + let header = &original_changelog[..start]; + let rest = &original_changelog[start..]; + Ok(format!("{header}\n{MARKER}\n{contents}\n{rest}")) + } + Ok(false) => Ok(format!("# Changelog\n\n{MARKER}\n{contents}")), + Err(e) => Err(e.into()), + } +} + +fn apply_changelog(entry: String) -> Result { + let path = std::path::PathBuf::from("CHANGELOG.md"); + let new_changelog = create_changelog(&path, &entry)?; + + let temp_file = tempfile::NamedTempFile::new_in(".")?; + let mut file = temp_file.as_file(); + file.write_all(new_changelog.as_bytes())?; + + Ok(FileReplacer { path, temp_file }) } diff --git a/src/error.rs b/src/error.rs index d9e3a60..a850b96 100644 --- a/src/error.rs +++ b/src/error.rs @@ -49,6 +49,8 @@ pub enum Error { ProjectBaseDirectory, #[error("unable to determine the most recent tag")] TagError, + #[error("changelog does not contain marker character")] + ChangelogMarker, } impl std::fmt::Debug for Error { diff --git a/src/replacers/mod.rs b/src/replacers/mod.rs index 784d0ee..9617506 100644 --- a/src/replacers/mod.rs +++ b/src/replacers/mod.rs @@ -11,6 +11,7 @@ pub trait Replacer { fn determine_replacements(self) -> Result>>; } +#[derive(Clone, Debug)] pub struct VersionReplacement { pub old_version: String, pub new_version: String,