1use std::env;
4
5use clap::Parser;
6use dialoguer::{theme::ColorfulTheme, Confirm, Editor, Input, Select};
7use gitcc_core::{Config, ConvcoMessage, StatusShow, StringExt};
8
9use crate::{error, info, new_line, success, warn};
10
11#[derive(Debug, Parser)]
13pub struct CommitArgs {}
14
15pub fn run(_args: CommitArgs) -> anyhow::Result<()> {
17 let cwd = env::current_dir()?;
19 let config = Config::load_from_fs(&cwd)?;
20 let config = if let Some(cfg) = config {
21 cfg
22 } else {
23 info!("using default config");
24 Config::default()
25 };
26
27 let status = gitcc_core::git_status(&cwd, StatusShow::Workdir)?;
29 if !status.is_empty() {
30 warn!("repo is dirty:");
31 for (file, _) in status {
32 eprintln!("\t{file}");
33 }
34 match Confirm::with_theme(&ColorfulTheme::default())
35 .with_prompt("continue ?")
36 .report(true)
37 .default(false)
38 .interact()?
39 {
40 false => {
41 error!("aborted");
42 return Ok(());
43 }
44 true => {}
45 }
46 }
47
48 let msg = open_dialogue(&config)?;
50 let commit = gitcc_core::commit_changes(&cwd, &msg.to_string())?;
54 new_line!();
55 success!(format!("new commit with id {}", commit.id));
56
57 Ok(())
58}
59
60fn open_dialogue(config: &Config) -> anyhow::Result<ConvcoMessage> {
62 let r#type = {
64 let commit_types: Vec<_> = config
65 .commit
66 .types
67 .iter()
68 .map(|(k, v)| format!("{}: {}", k, v))
69 .collect();
70 let commit_types_keys: Vec<_> = config.commit.types.keys().map(|k| k.to_string()).collect();
71 let i = Select::with_theme(&ColorfulTheme::default())
72 .items(&commit_types)
73 .clear(true)
74 .default(0)
75 .report(true)
76 .with_prompt("Commit type")
77 .interact()?;
78
79 commit_types_keys[i].clone()
80 };
81
82 let scope = {
84 let scope: String = Input::with_theme(&ColorfulTheme::default())
85 .with_prompt("Commit scope")
86 .report(true)
87 .allow_empty(true)
88 .interact_text()?;
89 if scope.is_empty() {
90 None
91 } else {
92 Some(scope.to_lowercase())
93 }
94 };
95
96 let desc = Input::<String>::with_theme(&ColorfulTheme::default())
98 .with_prompt("Commit description")
99 .report(true)
100 .interact()?
101 .trim()
102 .to_lowercase_first();
103
104 let mut msg = ConvcoMessage {
105 r#type,
106 scope,
107 is_breaking: false,
108 desc,
109 body: None,
110 footer: None,
111 };
112
113 match Confirm::with_theme(&ColorfulTheme::default())
116 .with_prompt("Breaking change ?")
117 .report(true)
118 .default(false)
119 .interact()?
120 {
121 false => {}
122 true => {
123 let breaking_change_desc = Input::<String>::with_theme(&ColorfulTheme::default())
124 .with_prompt("Breaking change description".to_string())
125 .report(true)
126 .allow_empty(true)
127 .interact_text()?;
128 msg.add_breaking_change(&breaking_change_desc);
129 }
130 }
131
132 match Confirm::with_theme(&ColorfulTheme::default())
134 .with_prompt("Commit message body ?")
135 .report(true)
136 .default(false)
137 .interact()?
138 {
139 false => {}
140 true => {
141 let text = Editor::new()
142 .require_save(true)
143 .trim_newlines(true)
144 .edit("")?;
145 msg.body = text;
146 }
147 }
148
149 'footer_notes: loop {
151 match Confirm::with_theme(&ColorfulTheme::default())
152 .with_prompt("Add footer note ?")
153 .report(true)
154 .default(false)
155 .interact()?
156 {
157 false => break 'footer_notes,
158 true => {
159 let key = Input::<String>::with_theme(&ColorfulTheme::default())
160 .with_prompt("Key ?".to_string())
161 .report(true)
162 .allow_empty(false)
163 .interact_text()?;
164 let value = Input::<String>::with_theme(&ColorfulTheme::default())
165 .with_prompt("Value ?".to_string())
166 .report(true)
167 .allow_empty(false)
168 .interact_text()?;
169 msg.add_footer_note(&key, &value);
170 }
171 }
172 }
173
174 Ok(msg)
175}