use std::fmt::Formatter;
use colored::Colorize;
use tera::{Context, Tera};
use tokio::task;
use super::prompts::{
commit_ai, commit_body, commit_breaking, commit_confirm, commit_issue,
commit_scope, commit_subject, commit_translate, commit_type,
};
use crate::{
ai::{gemini::Gemini, AI},
config,
error::ReviseResult,
git::GitUtils,
revise::prompts::Inquire,
AICommand, ReviseCommands,
};
#[derive(Debug, Default, Clone)]
pub struct Template {
pub commit_type: commit_type::Part,
pub commit_scope: commit_scope::Part,
pub commit_subject: commit_subject::Part,
pub commit_body: commit_body::Part,
pub commit_breaking: commit_breaking::Part,
pub commit_issue: commit_issue::Part,
}
impl Template {
pub async fn run(
&mut self,
cmd: &Option<ReviseCommands>,
) -> ReviseResult<String> {
if let Some(a) = cmd {
self.run_action(&a.ai).await?;
} else {
self.run_default()?;
}
let mut confirm = commit_confirm::Part::new(self.clone());
confirm.inquire()?;
Ok(confirm.ans.unwrap())
}
pub fn run_default(&mut self) -> ReviseResult<()> {
self.commit_type.inquire()?;
self.commit_scope.inquire()?;
self.commit_subject.inquire()?;
self.commit_body.inquire()?;
self.commit_breaking.inquire()?;
self.commit_issue.inquire()?;
Ok(())
}
pub async fn run_action(&mut self, cmd: &AICommand) -> ReviseResult<()> {
let cfg = config::get_config();
let Some(key) = cfg.api_key.get("gemini_key") else {
return Err(anyhow::anyhow!("API key not found"));
};
let gemini = Gemini::new(key);
let mut s = match cmd {
AICommand::Translate(s) => s.to_string(),
AICommand::Generate => GitUtils::new().diff()?,
};
if s.is_empty() {
let mut translate = commit_translate::Part::new();
translate.inquire()?;
s = match translate.ans {
Some(s) => s,
None => {
return Err(anyhow::anyhow!("Translate message is empty"));
}
};
}
let handle =
task::spawn(async move { gemini.generate_response(&s).await });
self.commit_type.inquire()?;
self.commit_scope.inquire()?;
self.commit_breaking.inquire()?;
self.commit_issue.inquire()?;
let res = handle.await??;
let mut ai = commit_ai::Part::new(res.keys().cloned().collect());
ai.inquire()?;
let ai_ans = res.get(&ai.ans.clone().unwrap()).unwrap();
self.commit_subject.ans = Some(ai_ans.message.clone());
self.commit_body.ans = Some(ai_ans.body.clone());
Ok(())
}
pub fn get_ctype(&self) -> String {
self.commit_type.ans.clone().unwrap()
}
pub fn get_cicon(&self) -> String {
config::get_config().get_emoji(&self.get_ctype()).unwrap()
}
pub fn get_cscope(&self) -> Option<String> {
self.commit_scope.ans.clone()
}
pub fn get_csubject(&self) -> String {
self.commit_subject.ans.clone().unwrap()
}
pub fn get_cbody(&self) -> Option<String> {
self.commit_body.ans.clone()
}
pub fn get_cbreaking(&self) -> Option<String> {
self.commit_breaking.ans.clone()
}
pub fn get_cissue(&self) -> Option<String> {
self.commit_issue.ans.clone()
}
pub fn template(&self, color: bool) -> String {
let cfg = config::get_config();
let mut tera = Tera::default();
tera.add_raw_template("template", &cfg.template).unwrap();
let mut ctx = Context::new();
if color {
ctx.insert("commit_type", &self.get_ctype().green().to_string());
ctx.insert("commit_icon", &self.get_cicon());
match self.get_cscope() {
Some(scope) => {
ctx.insert("commit_scope", &format!("{}", scope.yellow()));
}
None => {
ctx.insert("commit_scope", &Option::<String>::None);
}
}
ctx.insert(
"commit_subject",
&self.get_csubject().bright_cyan().to_string(),
);
match self.get_cbody() {
Some(body) => {
ctx.insert("commit_body", &body);
}
None => {
ctx.insert("commit_body", &Option::<String>::None);
}
}
if let Some(breaking) = self.get_cbreaking() {
ctx.insert(
"commit_breaking",
&format!(
"{}: {}",
"BREAKING CHANGE".bright_red(),
breaking.purple()
),
);
ctx.insert(
"commit_breaking_symbol",
&"!".bright_red().to_string(),
);
} else {
ctx.insert("commit_breaking", &Option::<String>::None);
ctx.insert("commit_breaking_symbol", &Option::<String>::None);
}
match self.get_cissue() {
Some(issue) => {
ctx.insert("commit_issue", &format!("{}", issue.blue()));
}
None => {
ctx.insert("commit_issue", &Option::<String>::None);
}
}
} else {
ctx.insert("commit_type", &self.get_ctype());
ctx.insert("commit_icon", &self.get_cicon());
ctx.insert("commit_scope", &self.get_cscope());
ctx.insert("commit_subject", &self.get_csubject());
ctx.insert("commit_body", &self.get_cbody());
if let Some(breaking) = self.get_cbreaking() {
ctx.insert(
"commit_breaking",
&format!("{}: {}", "BREAKING CHANGE", breaking),
);
ctx.insert("commit_breaking_symbol", &"!".to_string());
} else {
ctx.insert("commit_breaking", &Option::<String>::None);
ctx.insert("commit_breaking_symbol", &Option::<String>::None);
}
ctx.insert("commit_issue", &self.get_cissue());
}
tera.render("template", &ctx).unwrap()
}
}
impl std::fmt::Display for Template {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let msg = self.template(false);
write!(f, "{msg}")
}
}
#[ignore]
#[test]
fn test_template() {
config::initialize_config().unwrap_or_else(|e| {
eprintln!("Load config err: {e}");
std::process::exit(exitcode::CONFIG);
});
let t = Template {
commit_type: commit_type::Part {
ans: Some("feat".to_string()),
..Default::default()
},
commit_scope: commit_scope::Part {
ans: Some("scope".to_string()),
..Default::default()
},
commit_subject: commit_subject::Part {
ans: Some("add a new feature".to_string()),
..Default::default()
},
commit_body: commit_body::Part {
ans: Some("add a new feature with a body".to_string()),
..Default::default()
},
commit_breaking: commit_breaking::Part {
ans: Some("breaking change".to_string()),
..Default::default()
},
commit_issue: commit_issue::Part {
ans: Some("#34".to_string()),
..Default::default()
},
};
let s = t.template(true);
println!("{s}");
println!("{t}");
}