git_revise/revise/
template.rs1use std::fmt::Formatter;
2
3use colored::Colorize;
4use tera::{Context, Tera};
5use tokio::task;
6
7use super::prompts::{
8 commit_ai, commit_body, commit_breaking, commit_confirm, commit_issue,
9 commit_scope, commit_subject, commit_translate, commit_type,
10};
11use crate::{
12 ai::{gemini::Gemini, AI},
13 config,
14 error::ReviseResult,
15 git::GitUtils,
16 revise::prompts::Inquire,
17 AICommand, ReviseCommands,
18};
19
20#[derive(Debug, Default, Clone)]
21pub struct Template {
22 pub commit_type: commit_type::Part,
23 pub commit_scope: commit_scope::Part,
24 pub commit_subject: commit_subject::Part,
25 pub commit_body: commit_body::Part,
26 pub commit_breaking: commit_breaking::Part,
27 pub commit_issue: commit_issue::Part,
28}
29
30impl Template {
31 pub async fn run(&mut self, cmd: &ReviseCommands) -> ReviseResult<String> {
32 if cmd.ai.is_some() {
33 self.run_action(cmd).await?;
34 } else {
35 self.run_default()?;
36 }
37 let mut confirm = commit_confirm::Part::new(self.clone());
38 confirm.inquire()?;
39 Ok(confirm.ans.unwrap())
40 }
41
42 pub fn run_default(&mut self) -> ReviseResult<()> {
43 self.commit_type.inquire()?;
44 self.commit_scope.inquire()?;
45 self.commit_subject.inquire()?;
46 self.commit_body.inquire()?;
47 self.commit_breaking.inquire()?;
48 self.commit_issue.inquire()?;
49 Ok(())
50 }
51
52 pub async fn run_action(
53 &mut self,
54 cmd: &ReviseCommands,
55 ) -> ReviseResult<()> {
56 let cfg = config::get_config();
57 let Some(key) = cfg.api_key.get("gemini_key") else {
58 return Err(anyhow::anyhow!("API key not found"));
59 };
60
61 let gemini = Gemini::new(key);
62
63 let mut s = match cmd.ai.clone().unwrap() {
64 AICommand::Translate(s) => s,
65 AICommand::Generate => GitUtils::new().diff(&cmd.excludes)?,
66 };
67 if s.is_empty() {
69 let mut translate = commit_translate::Part::new();
70 translate.inquire()?;
71 s = match translate.ans {
72 Some(s) => s,
73 None => {
74 return Err(anyhow::anyhow!("Translate message is empty"));
75 }
76 };
77 }
78 let handle =
79 task::spawn(async move { gemini.generate_response(&s).await });
80 self.commit_type.inquire()?;
81 self.commit_scope.inquire()?;
82 self.commit_breaking.inquire()?;
83 self.commit_issue.inquire()?;
84 let res = handle.await??;
85 let mut ai = commit_ai::Part::new(res.keys().cloned().collect());
86 ai.inquire()?;
87 let ai_ans = res.get(&ai.ans.clone().unwrap()).unwrap();
88 self.commit_subject.ans = Some(ai_ans.message.clone());
89 self.commit_body.ans = Some(ai_ans.body.clone());
90 Ok(())
91 }
92
93 pub fn get_ctype(&self) -> String {
94 self.commit_type.ans.clone().unwrap()
95 }
96 pub fn get_cicon(&self) -> String {
97 config::get_config().get_emoji(&self.get_ctype()).unwrap()
98 }
99 pub fn get_cscope(&self) -> Option<String> {
100 self.commit_scope.ans.clone()
101 }
102 pub fn get_csubject(&self) -> String {
103 self.commit_subject.ans.clone().unwrap()
104 }
105 pub fn get_cbody(&self) -> Option<String> {
106 self.commit_body.ans.clone()
107 }
108 pub fn get_cbreaking(&self) -> Option<String> {
109 self.commit_breaking.ans.clone()
110 }
111 pub fn get_cissue(&self) -> Option<String> {
112 self.commit_issue.ans.clone()
113 }
114
115 pub fn template(&self, color: bool) -> String {
116 let cfg = config::get_config();
117 let mut tera = Tera::default();
118 tera.add_raw_template("template", &cfg.template).unwrap();
119 let mut ctx = Context::new();
120 if color {
121 ctx.insert("commit_type", &self.get_ctype().green().to_string());
122 ctx.insert("commit_icon", &self.get_cicon());
123 match self.get_cscope() {
124 Some(scope) => {
125 ctx.insert("commit_scope", &format!("{}", scope.yellow()));
126 }
127 None => {
128 ctx.insert("commit_scope", &Option::<String>::None);
129 }
130 }
131 ctx.insert(
132 "commit_subject",
133 &self.get_csubject().bright_cyan().to_string(),
134 );
135 match self.get_cbody() {
136 Some(body) => {
137 ctx.insert("commit_body", &body);
138 }
139 None => {
140 ctx.insert("commit_body", &Option::<String>::None);
141 }
142 }
143 if let Some(breaking) = self.get_cbreaking() {
144 ctx.insert(
145 "commit_breaking",
146 &format!(
147 "{}: {}",
148 "BREAKING CHANGE".bright_red(),
149 breaking.purple()
150 ),
151 );
152 ctx.insert(
153 "commit_breaking_symbol",
154 &"!".bright_red().to_string(),
155 );
156 } else {
157 ctx.insert("commit_breaking", &Option::<String>::None);
158 ctx.insert("commit_breaking_symbol", &Option::<String>::None);
159 }
160 match self.get_cissue() {
161 Some(issue) => {
162 ctx.insert("commit_issue", &format!("{}", issue.blue()));
163 }
164 None => {
165 ctx.insert("commit_issue", &Option::<String>::None);
166 }
167 }
168 } else {
169 ctx.insert("commit_type", &self.get_ctype());
170 ctx.insert("commit_icon", &self.get_cicon());
171 ctx.insert("commit_scope", &self.get_cscope());
172 ctx.insert("commit_subject", &self.get_csubject());
173 ctx.insert("commit_body", &self.get_cbody());
174 if let Some(breaking) = self.get_cbreaking() {
175 ctx.insert(
176 "commit_breaking",
177 &format!("{}: {}", "BREAKING CHANGE", breaking),
178 );
179 ctx.insert("commit_breaking_symbol", &"!".to_string());
180 } else {
181 ctx.insert("commit_breaking", &Option::<String>::None);
182 ctx.insert("commit_breaking_symbol", &Option::<String>::None);
183 }
184 ctx.insert("commit_issue", &self.get_cissue());
185 }
186
187 tera.render("template", &ctx).unwrap()
188 }
189}
190
191impl std::fmt::Display for Template {
192 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
193 let msg = self.template(false);
194 write!(f, "{msg}")
195 }
196}
197
198#[ignore]
199#[test]
200fn test_template() {
201 config::initialize_config().unwrap_or_else(|e| {
202 eprintln!("Load config err: {e}");
203 std::process::exit(exitcode::CONFIG);
204 });
205
206 let t = Template {
207 commit_type: commit_type::Part {
208 ans: Some("feat".to_string()),
209 ..Default::default()
210 },
211 commit_scope: commit_scope::Part {
212 ans: Some("scope".to_string()),
213 ..Default::default()
214 },
215 commit_subject: commit_subject::Part {
216 ans: Some("add a new feature".to_string()),
217 ..Default::default()
218 },
219 commit_body: commit_body::Part {
220 ans: Some("add a new feature with a body".to_string()),
221 ..Default::default()
222 },
223 commit_breaking: commit_breaking::Part {
224 ans: Some("breaking change".to_string()),
225 ..Default::default()
226 },
227 commit_issue: commit_issue::Part {
228 ans: Some("#34".to_string()),
229 ..Default::default()
230 },
231 };
232 let s = t.template(true);
233 println!("{s}");
234 println!("{t}");
235}