rlls 0.0.32

Cut a version, tag it, and publish a GitHub Release with raw git notes
use anyhow::Result;
use chrono::Utc;
use fs_err as fs;
use std::path::PathBuf;

pub fn maybe_update(
    cfg: &crate::config::Config,
    tag: &str,
    notes: &str,
    disabled: bool,
) -> Result<()> {
    if disabled {
        return Ok(());
    }

    let path = cfg
        .changelog_path
        .clone()
        .unwrap_or_else(|| "CHANGELOG.md".into());

    let mut out = String::new();
    let date = Utc::now().format("%Y-%m-%d").to_string();
    out.push_str(&format!("## {} {}\n\n", tag, date));

    let mut formatted_notes = String::new();
    let mut lines = notes.lines().peekable();

    while let Some(line) = lines.next() {
        if line.trim().starts_with("### Authors") {
            let author_count = notes.matches("\n- ").count();
            formatted_notes.push_str("### ");
            formatted_notes.push_str(if author_count > 1 {
                "Authors\n"
            } else {
                "Author\n"
            });

            for l in lines.by_ref() {
                if l.trim().is_empty() {
                    formatted_notes.push('\n');
                    break;
                }

                if let Some((name, count_part)) =
                    l.trim_start_matches("- ").split_once('(')
                {
                    let count_str =
                        count_part.trim_end_matches(')').trim();
                    let number = count_str
                        .split_whitespace()
                        .next()
                        .unwrap_or("0")
                        .parse::<u32>()
                        .unwrap_or(0);
                    let plural = if number == 1 {
                        "commit"
                    } else {
                        "commits"
                    };
                    formatted_notes.push_str(&format!(
                        "- {} ({} {})\n",
                        name.trim(),
                        number,
                        plural
                    ));
                } else {
                    formatted_notes.push_str(l);
                    formatted_notes.push('\n');
                }
            }
        } else {
            formatted_notes.push_str(line);
            formatted_notes.push('\n');
        }
    }

    out.push_str(&formatted_notes);
    out.push('\n');

    let p = PathBuf::from(&path);
    let existing = if p.exists() {
        fs::read_to_string(&p).unwrap_or_default()
    } else {
        String::new()
    };

    let mut final_doc = String::new();
    if let Some(h) = &cfg.changelog_header {
        if !existing.starts_with(h) {
            final_doc.push_str(h);
            final_doc.push('\n');
            final_doc.push('\n');
        }
    }

    final_doc.push_str(&out);
    final_doc.push_str(&existing);

    fs::write(&p, final_doc)?;
    eprintln!("[ok] updated changelog at {}", p.display());
    Ok(())
}