jj-cz 1.1.0

Conventional commits for Jujutsu
Documentation
use clap::Parser;

/// Interactive conventional commit tool for Jujutsu
///
/// Guides you through creating a properly formatted conventional commit message
/// and applies it to the current change in your Jujutsu repository.
#[derive(Debug, Parser)]
#[command(
    name = "jj-cz",
    version = concat!(env!("CARGO_PKG_VERSION"), " (jj-lib ", env!("JJ_LIB_VERSION") ,")"),
    about = "Interactive conventional commit tool for Jujutsu",
    long_about = "Guides you through creating a properly formatted conventional \
                  commit message and applies it to the current change in your \
                  Jujutsu repository.\n\n\
                  This tool requires an interactive terminal (TTY)."
)]
pub struct Cli {
    /// The revision(s) whose description to edit (default: @)
    #[arg(value_name = "REVSETS")]
    revsets: Vec<String>,

    /// Create a new child revision after editing the description
    #[arg(short, long)]
    new: bool,
}

impl Cli {
    /// Returns the revsets to operate on, defaulting to `["@"]` if none provided
    pub fn revsets(&self) -> Vec<&str> {
        if self.revsets.is_empty() {
            vec!["@"]
        } else {
            self.revsets.iter().map(|s| s.as_str()).collect()
        }
    }

    pub fn create_new(&self) -> bool {
        self.new
    }

    pub fn validate(&self) -> Result<(), jj_cz::Error> {
        if self.new && self.revsets().len() > 1 {
            Err(jj_cz::Error::NewFlagWithMultipleRevisions)
        } else {
            Ok(())
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use clap::Parser;

    #[test]
    fn revsets_defaults_to_at() {
        let cli = Cli::parse_from(["jj-cz"]);
        assert_eq!(cli.revsets(), vec!["@"]);
    }

    #[test]
    fn revsets_returns_provided_values() {
        let cli = Cli::parse_from(["jj-cz", "abc", "def"]);
        let revsets = cli.revsets();
        assert_eq!(revsets, vec!["abc", "def"]);
    }

    #[test]
    fn revsets_single_revset() {
        let cli = Cli::parse_from(["jj-cz", "xyz"]);
        assert_eq!(cli.revsets(), vec!["xyz"]);
    }

    #[test]
    fn create_new_returns_false_by_default() {
        let cli = Cli::parse_from(["jj-cz"]);
        assert!(!cli.create_new());
    }

    #[test]
    fn create_new_returns_true_with_flag() {
        let cli = Cli::parse_from(["jj-cz", "--new"]);
        assert!(cli.create_new());
    }

    #[test]
    fn create_new_returns_true_with_short_flag() {
        let cli = Cli::parse_from(["jj-cz", "-n"]);
        assert!(cli.create_new());
    }

    #[test]
    fn validate_ok_with_no_args() {
        let cli = Cli::parse_from(["jj-cz"]);
        assert!(cli.validate().is_ok());
    }

    #[test]
    fn validate_ok_with_new_and_single_revset() {
        let cli = Cli::parse_from(["jj-cz", "--new", "@"]);
        assert!(cli.validate().is_ok());
    }

    #[test]
    fn validate_ok_with_multiple_revsets_no_new() {
        let cli = Cli::parse_from(["jj-cz", "abc", "def"]);
        assert!(cli.validate().is_ok());
    }

    #[test]
    fn validate_err_with_new_and_multiple_revsets() {
        let cli = Cli::parse_from(["jj-cz", "--new", "abc", "def"]);
        let result = cli.validate();
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            jj_cz::Error::NewFlagWithMultipleRevisions
        ));
    }

    #[test]
    fn cli_derives_debug() {
        let cli = Cli::parse_from(["jj-cz", "--new", "@"]);
        let debug = format!("{:?}", cli);
        assert!(debug.contains("Cli"));
    }
}