use crate::config::Config;
use anyhow::Result;
use dialoguer::{Confirm, Input, Select, theme::ColorfulTheme};
#[derive(Debug, Clone)]
pub struct CommitWizardResult {
pub r#type: String,
pub scope: Option<String>,
pub message: String,
pub body: Option<String>,
pub breaking: bool,
pub breaking_description: Option<String>,
pub tag: Option<String>,
pub issue: Option<String>,
}
#[derive(Debug, Clone)]
pub struct BranchWizardResult {
pub branch_type: String,
pub name: String,
pub issue: Option<String>,
pub from_commit: Option<String>,
}
#[derive(Debug, Clone)]
pub struct CompleteWizardResult {
pub branch_type: String,
pub name: String,
}
#[derive(Debug, Clone)]
pub struct ChangeLogWizardResult {
pub from: Option<String>,
pub to: Option<String>,
pub unreleased: bool,
}
pub fn run_commit_wizard(config: &Config) -> Result<CommitWizardResult> {
let theme = ColorfulTheme::default();
let allowed_types = config
.lint
.as_ref()
.and_then(|l| l.conventional_commit_type.as_ref())
.and_then(|cct| cct.allowed_types.as_ref())
.cloned()
.unwrap_or_else(|| {
vec![
"feat".to_string(),
"fix".to_string(),
"chore".to_string(),
"docs".to_string(),
"style".to_string(),
"refactor".to_string(),
"perf".to_string(),
"test".to_string(),
"build".to_string(),
"ci".to_string(),
"revert".to_string(),
"wip".to_string(),
]
});
let type_selection = Select::with_theme(&theme)
.with_prompt("Select the type of change")
.items(&allowed_types)
.default(0)
.interact()?;
let r#type = allowed_types[type_selection].clone();
fn to_option(s: String) -> Option<String> {
if s.is_empty() { None } else { Some(s) }
}
let scope: Option<String> = to_option(
Input::<String>::with_theme(&theme)
.with_prompt("Enter the scope of this change (optional)")
.allow_empty(true)
.interact_text()?,
);
let message: String = Input::with_theme(&theme)
.with_prompt("Write a short, imperative tense description of the change")
.interact_text()?;
let body: Option<String> = to_option(
Input::<String>::with_theme(&theme)
.with_prompt("Provide a longer description of the change (optional)")
.allow_empty(true)
.interact_text()?,
);
let breaking = Confirm::with_theme(&theme)
.with_prompt("Is this a breaking change?")
.default(false)
.interact()?;
let breaking_description: Option<String> = if breaking {
Some(
Input::<String>::with_theme(&theme)
.with_prompt("Describe the breaking change")
.interact_text()?,
)
} else {
None
};
let issue: Option<String> = to_option(
Input::<String>::with_theme(&theme)
.with_prompt("Enter an issue reference (e.g., PROJ-123) (optional)")
.allow_empty(true)
.interact_text()?,
);
let tag: Option<String> = to_option(
Input::<String>::with_theme(&theme)
.with_prompt("Enter a tag for this commit (optional)")
.allow_empty(true)
.interact_text()?,
);
Ok(CommitWizardResult {
r#type,
scope,
message,
body,
breaking,
breaking_description,
tag,
issue,
})
}
pub fn run_branch_wizard(config: &Config) -> Result<BranchWizardResult> {
let theme = ColorfulTheme::default();
let mut allowed_types: Vec<String> = config.branch_types.keys().cloned().collect();
allowed_types.sort();
let type_selection = Select::with_theme(&theme)
.with_prompt("Select the type of branch")
.items(&allowed_types)
.default(0)
.interact()?;
let branch_type = allowed_types[type_selection].clone();
let name: String = Input::with_theme(&theme)
.with_prompt("Enter a short, descriptive name for the branch (use hyphens)")
.interact_text()?;
let issue: Option<String> = {
let input: String = Input::<String>::with_theme(&theme)
.with_prompt("Enter an issue reference to include in the branch name (optional)")
.allow_empty(true)
.interact_text()?;
if input.is_empty() { None } else { Some(input) }
};
let from_commit: Option<String> = {
let input: String = Input::<String>::with_theme(&theme)
.with_prompt("Enter a commit hash on 'main' to branch from (optional)")
.allow_empty(true)
.interact_text()?;
if input.is_empty() { None } else { Some(input) }
};
Ok(BranchWizardResult {
branch_type,
name,
issue,
from_commit,
})
}
pub fn run_complete_wizard(config: &Config) -> Result<CompleteWizardResult> {
let theme = ColorfulTheme::default();
let mut allowed_types: Vec<String> = config.branch_types.keys().cloned().collect();
allowed_types.sort();
let type_selection = Select::with_theme(&theme)
.with_prompt("Select the type of branch to complete")
.items(&allowed_types)
.default(0)
.interact()?;
let branch_type = allowed_types[type_selection].clone();
let name: String = Input::with_theme(&theme)
.with_prompt("Enter the name of the branch to complete")
.interact_text()?;
Ok(CompleteWizardResult { branch_type, name })
}
pub fn run_changelog_wizard() -> Result<ChangeLogWizardResult> {
let theme = ColorfulTheme::default();
let options = &[
"Generate for unreleased changes (since the latest tag)",
"Generate for a specific range of tags",
];
let selection = Select::with_theme(&theme)
.with_prompt("What changelog would you like to generate?")
.items(options)
.default(0)
.interact()?;
match selection {
0 => Ok(ChangeLogWizardResult {
from: None,
to: None,
unreleased: true,
}),
1 => {
let from: String = Input::with_theme(&theme)
.with_prompt("Enter the 'from' tag (e.g., v0.12.0)")
.interact_text()?;
let to: String = Input::with_theme(&theme)
.with_prompt("Enter the 'to' tag (e.g., v0.13.0, optional)")
.allow_empty(true)
.interact_text()?;
Ok(ChangeLogWizardResult {
from: Some(from),
to: if to.is_empty() { None } else { Some(to) },
unreleased: false,
})
}
_ => unreachable!(),
}
}