utpm 0.3.0

UTPM is a package manager for local and remote Typst packages. Quickly create and manage projects and templates on your system, and publish them directly to Typst Universe.
Documentation
use std::borrow::Cow;
use std::fs::{File, read_to_string, write};
use std::io::Write;
use std::str::FromStr;

use itertools::Itertools;
use toml_edit::{DocumentMut, value};
use tracing::instrument;
use typst_syntax::package::PackageVersion;

use crate::{
    utils::{self, dryrun::get_dry_run, state::Result},
    utpm_log,
};

use super::BumpArgs;

/// Wraps a value in HTML-like tags if a tag is provided.
///
/// # Arguments
/// * `tag` - Optional tag name (e.g., "version")
/// * `value` - The value to wrap
///
/// # Returns
/// If tag is Some, returns `<tag>value</tag>`. Otherwise, returns the value unchanged.
fn tag_change<'a>(tag: Option<&str>, value: &'a str) -> Cow<'a, str> {
    if let Some(tag) = tag {
        Cow::Owned(format!("<{tag}>{value}</{tag}>"))
    } else {
        Cow::Borrowed(value)
    }
}

/// Bumps the version number in typst.toml and optionally in other files.
///
/// Updates the package version in the manifest and searches for the old version
/// in specified files, replacing it with the new version. Supports wrapping
/// versions in HTML-like tags for special file formats.
#[instrument(skip(cmd))]
pub async fn run(cmd: &BumpArgs) -> Result<bool> {
    utpm_log!(trace, "executing bump command");
    let manifest_path = utils::try_find_path(&utils::paths::get_current_dir()?)?;
    let mut config = read_to_string(&manifest_path)?.parse::<DocumentMut>()?;

    let old_version = config["package"]["version"].as_str().unwrap();
    let new_version = cmd.new_version.as_str();
    PackageVersion::from_str(new_version).unwrap();

    let files = &cmd.include;

    for file in files {
        let mut string = read_to_string(file)?;
        utpm_log!(info, "Found {}", file);
        let old_version = tag_change(cmd.tag.as_deref(), old_version);
        let new_version = tag_change(cmd.tag.as_deref(), new_version);
        string = string.replace(old_version.as_ref(), &new_version);
        if !get_dry_run() {
            write(file, string)?;
        }
        utpm_log!(info, "Modified {}", file);
    }

    let files = ["typst.toml"]
        .into_iter()
        .chain(files.iter().map(AsRef::<str>::as_ref))
        .join(", ");

    if !get_dry_run() {
        config["package"]["version"] = value(new_version);
        let mut file = File::create(manifest_path)?;
        write!(file, "{}", config)?;
        file.sync_all()?;
    }

    utpm_log!(info, format!("New version: {new_version}"), "version" => new_version, "files" => files);
    Ok(true)
}