1use std::process::exit;
2
3use console::Term;
4use tracing::{info, warn};
5use url::Url;
6
7use crate::git::has_staged_changes;
8use crate::{git, EmojiFormat, Error, GitmojiConfig, Result, EXIT_CANNOT_UPDATE, EXIT_NO_CONFIG};
9
10mod commit;
11pub use self::commit::*;
12
13mod config;
14pub use self::config::*;
15
16#[cfg(feature = "hook")]
17mod hook;
18
19mod list;
20use self::list::print_gitmojis;
21
22mod search;
23use self::search::filter;
24
25mod update;
26use self::update::update_gitmojis;
27
28async fn get_config_or_stop() -> GitmojiConfig {
29 match read_config_or_fail().await {
30 Ok(config) => config,
31 Err(err) => {
32 warn!("Oops, cannot read config because {err}");
33 eprintln!(
34 "⚠️ No configuration found, try run `gitmoji init` to fetch a configuration"
35 );
36 exit(EXIT_NO_CONFIG)
37 }
38 }
39}
40
41async fn update_config_or_stop(config: GitmojiConfig) -> GitmojiConfig {
42 let url = config.update_url().to_string();
43 match update_gitmojis(config).await {
44 Ok(config) => config,
45 Err(err) => {
46 warn!("Oops, cannot update the config because {err}");
47 eprintln!("⚠️ Configuration not updated, maybe check the update url '{url}'");
48 exit(EXIT_CANNOT_UPDATE)
49 }
50 }
51}
52
53#[derive(Debug, Clone)]
54struct CommitTitleDescription {
55 title: String,
56 description: Option<String>,
57}
58
59#[tracing::instrument(skip(term))]
60async fn ask_commit_title_description(
61 config: &GitmojiConfig,
62 term: &Term,
63) -> Result<CommitTitleDescription> {
64 let CommitParams {
65 gitmoji,
66 scope,
67 title,
68 description,
69 } = get_commit_params(config, term)?;
70
71 let gitmoji = match config.format() {
72 EmojiFormat::UseCode => gitmoji.code(),
73 EmojiFormat::UseEmoji => gitmoji.emoji(),
74 };
75
76 let title = scope.map_or_else(
77 || format!("{gitmoji} {title}"),
78 |scope| format!("{gitmoji} {scope}{title}"),
79 );
80
81 let result = CommitTitleDescription { title, description };
82 Ok(result)
83}
84
85#[tracing::instrument(skip(term))]
87pub async fn commit(all: bool, amend: bool, term: &Term) -> Result<()> {
88 let config = get_config_or_stop().await;
89
90 if !amend && !has_staged_changes().await? {
91 eprintln!("No change to commit");
92 return Ok(());
93 }
94
95 let CommitTitleDescription { title, description } =
96 ask_commit_title_description(&config, term).await?;
97
98 let all = all || config.auto_add();
100
101 let status = git::commit(all, amend, config.signed(), &title, description.as_deref()).await?;
103 status.success().then_some(()).ok_or(Error::FailToCommit)
104}
105
106#[tracing::instrument(skip(term))]
108pub async fn config(default: bool, term: &Term) -> Result<()> {
109 let config = if default {
110 GitmojiConfig::default()
111 } else {
112 create_config(term)?
113 };
114 info!("Loading gitmojis from {}", config.update_url());
115 update_config_or_stop(config).await;
116
117 Ok(())
118}
119
120#[tracing::instrument]
122pub async fn search(text: &str) -> Result<()> {
123 let config = get_config_or_stop().await;
124 let result = filter(config.gitmojis(), text);
125 print_gitmojis(&result);
126 Ok(())
127}
128
129#[tracing::instrument]
131pub async fn list() -> Result<()> {
132 let config = get_config_or_stop().await;
133 print_gitmojis(config.gitmojis());
134 Ok(())
135}
136
137#[tracing::instrument]
139pub async fn update_config(url: Option<Url>) -> Result<()> {
140 let mut config = read_config_or_default().await;
141 if let Some(url) = url {
142 config.set_update_url(url);
143 }
144 let result = update_config_or_stop(config).await;
145 print_gitmojis(result.gitmojis());
146
147 Ok(())
148}
149
150#[cfg(feature = "hook")]
152#[tracing::instrument]
153pub async fn create_hook() -> Result<()> {
154 hook::create().await
155}
156
157#[tracing::instrument]
159#[cfg(feature = "hook")]
160pub async fn remove_hook() -> Result<()> {
161 hook::remove().await
162}
163
164#[cfg(feature = "hook")]
166#[tracing::instrument(skip(term))]
167pub async fn apply_hook(
168 dest: std::path::PathBuf,
169 source: Option<String>,
170 term: &Term,
171) -> Result<()> {
172 use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
173
174 let config = get_config_or_stop().await;
175
176 let CommitTitleDescription { title, description } =
177 ask_commit_title_description(&config, term).await?;
178
179 info!("Write commit message to {dest:?} with source: {source:?}");
180 let mut file = tokio::fs::File::create(dest).await?;
181
182 let mut contents = vec![];
183 file.read_to_end(&mut contents).await?;
184 file.seek(std::io::SeekFrom::Start(0)).await?;
185
186 file.write_all(title.as_bytes()).await?;
187 file.write_all(b"\n\n").await?;
188
189 if let Some(description) = description {
190 file.write_all(description.as_bytes()).await?;
191 file.write_all(b"\n").await?;
192 }
193 file.write_all(&contents).await?;
194
195 Ok(())
196}