changen 0.1.10

Helper program to manage a changelog
Documentation
use anyhow::bail;
use changelog::{ser::serialize_changelog, utils::DEFAULT_UNRELEASED, ChangeLog};

use crate::{
    config::MergeDevVersions,
    git_provider::DiffTags,
    repository::{try_detect_new_version, Repository},
};

pub fn release<R: Repository>(
    r: &R,
    mut changelog: ChangeLog,
    options: &crate::config::Release,
) -> anyhow::Result<(String, String)> {
    let crate::config::Release {
        file: _,
        version,
        previous_version,
        provider,
        repo,
        header,
        merge_dev_versions,
        omit_diff,
        stdout: _,
        force,
    } = options;

    let new_version = try_detect_new_version(r, version.clone())?;

    if changelog.releases.contains_key(&new_version) {
        if *force {
            changelog.releases.remove(&new_version);
            eprintln!("The release {} will be overwritten", new_version)
        } else {
            bail!(
                "Version {} already exist. Create a new tag or use the --version option. You can also use the --force option to override the existing release.",
                new_version
            );
        }
    };

    let mut prev_unreleased = changelog
        .unreleased
        .replace(DEFAULT_UNRELEASED.clone())
        .unwrap_or(DEFAULT_UNRELEASED.clone());

    prev_unreleased.title.version = new_version.to_string();

    if let Some(header) = header {
        match &prev_unreleased.header {
            Some(prev_header) => {
                prev_unreleased.header = Some(format!("{}\n\n{}", header, prev_header))
            }
            None => prev_unreleased.header = Some(header.clone()),
        }
    }

    if let Some(repo) = &repo {
        match provider.release_link(repo, &new_version.to_string()) {
            Ok(link) => {
                prev_unreleased.title.release_link = Some(link);
            }
            Err(e) => {
                eprintln!("{e}");
            }
        }
    }

    match merge_dev_versions {
        MergeDevVersions::Yes | MergeDevVersions::Auto
            if let Some(new_version_semver) = new_version.version_opt()
                && new_version_semver.pre.is_empty() =>
        {
            let dev_releases = changelog
                .releases
                .extract_if(|k, _| {
                    k.version_opt()
                        .map(|k| {
                            k.major == new_version_semver.major
                                && k.minor == new_version_semver.minor
                                && k.patch == new_version_semver.patch
                        })
                        .unwrap_or(false)
                })
                .collect::<Vec<_>>();

            for (_, dev_release) in dev_releases {
                prev_unreleased
                    .insert_release_notes(dev_release.note_sections.into_iter().map(|(_, sec)| sec))
            }
        }
        _ => {}
    }

    let previous_version = previous_version
        .clone()
        .or_else(|| changelog.last_version());

    let diff_tags = DiffTags::new(new_version, previous_version)?;

    if !omit_diff {
        let link = if let Some(repo) = &repo {
            match provider.diff_link(repo, &diff_tags) {
                Ok(link) => Some(link),
                Err(e) => {
                    eprintln!("{e}");
                    None
                }
            }
        } else {
            None
        };

        if let Some(link) = link {
            let line = format!("_Full Changelog: {link}_");

            match &mut prev_unreleased.footer {
                Some(footer) => {
                    footer.push_str("\n\n");
                    footer.push_str(&line);
                }
                None => {
                    prev_unreleased.footer = Some(line);
                }
            }
        }
    }

    changelog
        .releases
        .insert(diff_tags.new.clone(), prev_unreleased);

    debug!("release: serialize changelog: {:?}", changelog);

    changelog.sanitize(&changelog::fmt::Options::default());

    let output = serialize_changelog(&changelog, &changelog::ser::Options::default());

    Ok((diff_tags.new.to_string(), output))
}