cargo-tag 0.1.14

Cargo plugin to bump crate's versions and Git tag them for release
Documentation
use anyhow::Result;
use clap::{Parser, Subcommand};

use crate::cargo_toml::CargoToml;
use crate::git::Git;
use crate::version::Version;

const ABOUT: &str = r#"Cargo plugin to bump crate's versions and Git tag them
for release.

"cargo tag" helps to automate the process of bumping versions
similar to how "npm version" does.

When bumping versions with "cargo tag", the
Cargo.toml and Cargo.lock files are updated with the new version, then a Git
commit and a Git tag are both created."#;

#[derive(Parser)]
#[command(bin_name = "cargo")]
#[command(next_line_help = true)]
#[command(name = "cargo", author, version, about, long_about = Some(ABOUT))]
pub enum Cli {
    /// Bump crate's version and create a Git tag
    Tag(TagArgs),
}

#[derive(clap::Args, Debug)]
pub struct TagArgs {
    #[command(subcommand)]
    pub command: Command,

    /// Prefix string for Git tags
    #[arg(short, long)]
    pub prefix: Option<String>,

    /// Skip creating a Git commit
    #[arg(long)]
    pub no_commit: bool,

    /// Skip creating a Git tag
    #[arg(long)]
    pub no_tag: bool,

    /// Get name and email from environment variables CARGO_TAG_NAME and CARGO_TAG_EMAIL.
    /// They must be set beforehand.
    #[arg(long)]
    pub env: bool,

    /// Generate tag without writing files or creating git commits/tags
    #[arg(long)]
    pub dry_run: bool,
}

#[derive(Clone, Subcommand, Debug)]
pub enum Command {
    /// Print current package version
    Current,
    /// Bumps crate's minor version and create a git tag
    Minor,
    /// Bumps crate's major version and create a git tag
    Major,
    /// Bumps crate's patch version and create a git tag
    Patch,
    /// Sets the provided prerelease string and create a git tag
    #[clap(name = "prerelease")]
    PreRelease { prerelease: String },
}

impl Command {
    pub fn exec(&self, args: &TagArgs) -> Result<()> {
        let cargo_toml = CargoToml::open()?;
        let mut version = Version::from(&cargo_toml.manifest.package().version);

        match *self {
            Command::Current => {
                println!("{}", cargo_toml.manifest.package().version);
                return Ok(());
            }
            Command::Major | Command::Minor | Command::Patch => {
                match self {
                    Command::Major => version.bump_major(),
                    Command::Minor => version.bump_minor(),
                    Command::Patch => version.bump_patch(),
                    _ => unreachable!(),
                };

                if !args.dry_run {
                    cargo_toml.write_version(&version)?;
                    cargo_toml.run_cargo_fetch()?;
                }
            }
            Command::PreRelease { ref prerelease } => {
                version.set_prerelease(prerelease)?;
                if !args.dry_run {
                    cargo_toml.write_version(&version)?;
                    cargo_toml.run_cargo_fetch()?;
                }
            }
        }

        let prefix = args.prefix.clone().unwrap_or_default();
        let version_str = prefix + &version.to_string();

        if !args.dry_run {
            let repository = if args.env {
                Git::from_env("main")?
            } else {
                Git::from_git_config("main")?
            };

            if !args.no_commit {
                repository.commit(&format!("chore: bump version to {}", version_str))?;
            }

            if !args.no_tag {
                repository.tag(
                    &version_str,
                    &format!("chore: bump version to {}", version_str),
                )?;
            }
        }

        println!("{version_str}");

        Ok(())
    }
}