gitmoji_rs/cmd/
mod.rs

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/// Commit using Gitmoji
86#[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    // Add before commit
99    let all = all || config.auto_add();
100
101    // Commit
102    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/// Configure Gitmoji
107#[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/// Search a gitmoji
121#[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/// List all Gitmojis
130#[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/// Update the configuration with the URL
138#[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/// Create hook
151#[cfg(feature = "hook")]
152#[tracing::instrument]
153pub async fn create_hook() -> Result<()> {
154    hook::create().await
155}
156
157/// Remove hook
158#[tracing::instrument]
159#[cfg(feature = "hook")]
160pub async fn remove_hook() -> Result<()> {
161    hook::remove().await
162}
163
164/// Apply hook
165#[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}