gitmoji_rs/
model.rs

1use std::fmt::{self, Display};
2
3use jiff::Timestamp;
4use serde::{Deserialize, Serialize};
5use url::Url;
6
7/// The default URL used for update
8pub const DEFAULT_URL: &str = "https://gitmoji.dev/api/gitmojis";
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11/// The emoji format
12pub enum EmojiFormat {
13    /// Use the code mode, like ':smile:'
14    UseCode,
15    /// Use the emoji mode, like '😄'
16    UseEmoji,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(default)]
21/// The Gitmojis configuration
22pub struct GitmojiConfig {
23    auto_add: bool,
24    format: EmojiFormat,
25    signed: bool,
26    scope: bool,
27    update_url: Url,
28    last_update: Option<Timestamp>,
29    gitmojis: Vec<Gitmoji>,
30}
31
32impl GitmojiConfig {
33    /// Create a new `GitmojiConfig`
34    #[must_use]
35    pub const fn new(
36        auto_add: bool,
37        format: EmojiFormat,
38        signed: bool,
39        scope: bool,
40        update_url: Url,
41    ) -> Self {
42        Self {
43            auto_add,
44            format,
45            signed,
46            scope,
47            update_url,
48            last_update: None,
49            gitmojis: vec![],
50        }
51    }
52
53    /// Merge with a local configuration
54    pub fn merge(&mut self, local_config: &LocalGitmojiConfig) {
55        if let Some(auto_add) = local_config.auto_add() {
56            self.auto_add = auto_add;
57        }
58        if let Some(format) = local_config.format() {
59            self.format = format;
60        }
61        if let Some(signed) = local_config.signed() {
62            self.signed = signed;
63        }
64        if let Some(gitmojis) = local_config.gitmojis() {
65            self.gitmojis = gitmojis.to_vec();
66        }
67    }
68
69    /// If the "--all" is added to commit command
70    #[must_use]
71    pub const fn auto_add(&self) -> bool {
72        self.auto_add
73    }
74
75    /// The format of gitmoji (code or emoji)
76    #[must_use]
77    pub const fn format(&self) -> &EmojiFormat {
78        &self.format
79    }
80
81    /// If the signed commits is enabled
82    #[must_use]
83    pub const fn signed(&self) -> bool {
84        self.signed
85    }
86
87    /// If we add a scope
88    #[must_use]
89    pub const fn scope(&self) -> bool {
90        self.scope
91    }
92
93    /// The URL used for update
94    #[must_use]
95    pub fn update_url(&self) -> &str {
96        self.update_url.as_ref()
97    }
98
99    /// Set the URL used for update
100    pub fn set_update_url(&mut self, update_url: Url) {
101        self.update_url = update_url;
102    }
103
104    /// The last time the gitmoji list was updated
105    #[must_use]
106    pub const fn last_update(&self) -> Option<Timestamp> {
107        self.last_update
108    }
109
110    /// The gitmoji list
111    #[must_use]
112    pub fn gitmojis(&self) -> &[Gitmoji] {
113        self.gitmojis.as_ref()
114    }
115
116    /// Set the gitmojis list
117    pub fn set_gitmojis(&mut self, gitmojis: Vec<Gitmoji>) {
118        self.last_update = Some(Timestamp::now());
119        self.gitmojis = gitmojis;
120    }
121}
122
123impl Default for GitmojiConfig {
124    fn default() -> Self {
125        Self {
126            auto_add: false,
127            format: EmojiFormat::UseCode,
128            signed: false,
129            scope: false,
130            update_url: DEFAULT_URL.parse().expect("It's a valid URL"),
131            last_update: None,
132            gitmojis: vec![],
133        }
134    }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
138/// The local gitmoji configuration
139pub struct LocalGitmojiConfig {
140    auto_add: Option<bool>,
141    format: Option<EmojiFormat>,
142    signed: Option<bool>,
143    scope: Option<bool>,
144    gitmojis: Option<Vec<Gitmoji>>,
145}
146
147impl LocalGitmojiConfig {
148    /// If the "--all" is added to commit command
149    #[must_use]
150    pub fn auto_add(&self) -> Option<bool> {
151        self.auto_add
152    }
153
154    /// The format of gitmoji (code or emoji)
155    #[must_use]
156    pub fn format(&self) -> Option<EmojiFormat> {
157        self.format
158    }
159
160    /// If the signed commits is enabled
161    #[must_use]
162    pub fn signed(&self) -> Option<bool> {
163        self.signed
164    }
165
166    /// If we add a scope
167    #[must_use]
168    pub fn scope(&self) -> Option<bool> {
169        self.scope
170    }
171
172    /// The gitmoji list
173    #[must_use]
174    pub fn gitmojis(&self) -> Option<&[Gitmoji]> {
175        self.gitmojis.as_deref()
176    }
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
180/// A Gitmoji
181pub struct Gitmoji {
182    emoji: String,
183    code: String,
184    name: Option<String>,
185    description: Option<String>,
186}
187
188impl Gitmoji {
189    /// Create a gitmoji
190    #[must_use]
191    pub fn new(
192        emoji: String,
193        code: String,
194        name: Option<String>,
195        description: Option<String>,
196    ) -> Self {
197        Self {
198            emoji,
199            code,
200            name,
201            description,
202        }
203    }
204
205    /// The emoji
206    #[must_use]
207    pub fn emoji(&self) -> &str {
208        self.emoji.as_ref()
209    }
210
211    /// The associated code
212    #[must_use]
213    pub fn code(&self) -> &str {
214        self.code.as_ref()
215    }
216
217    /// The name
218    #[must_use]
219    pub fn name(&self) -> Option<&str> {
220        self.name.as_deref()
221    }
222
223    /// The description
224    #[must_use]
225    pub fn description(&self) -> Option<&str> {
226        self.description.as_deref()
227    }
228}
229
230impl Display for Gitmoji {
231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232        let Gitmoji {
233            emoji,
234            code,
235            name,
236            description,
237            ..
238        } = self;
239        write!(
240            f,
241            "{emoji} {code} {} - {}",
242            name.as_deref().unwrap_or_default(),
243            description.as_deref().unwrap_or_default()
244        )
245    }
246}
247
248#[cfg(test)]
249#[allow(clippy::ignored_unit_patterns)]
250mod tests {
251    use assert2::*;
252
253    use super::*;
254
255    #[test]
256    fn should_serde_gitmoji() {
257        let gitmoji = Gitmoji {
258            emoji: String::from("🚀"),
259            code: String::from("rocket"),
260            name: Some(String::from("Initialize")),
261            description: Some(String::from("Bla bla")),
262        };
263
264        // Serialize
265        let toml = toml_edit::ser::to_string(&gitmoji);
266        let_assert!(Ok(toml) = toml);
267
268        // Deserialize
269        let result = toml_edit::de::from_str::<Gitmoji>(&toml);
270        let_assert!(Ok(result) = result);
271
272        check!(result == gitmoji);
273    }
274
275    #[test]
276    fn should_serde_config() {
277        let mut config = GitmojiConfig::default();
278        config.gitmojis.push(Gitmoji {
279            emoji: String::from("🚀"),
280            code: String::from("rocket"),
281            name: Some(String::from("Initialize")),
282            description: Some(String::from("Bla bla")),
283        });
284
285        // Serialize
286        let toml = toml_edit::ser::to_string(&config);
287        let_assert!(Ok(toml) = toml);
288
289        // Deserialize
290        let result = toml_edit::de::from_str::<GitmojiConfig>(&toml);
291        let_assert!(Ok(result) = result);
292
293        check!(result == config);
294    }
295}