1use std::fmt::{self, Display};
2
3use jiff::Timestamp;
4use serde::{Deserialize, Serialize};
5use url::Url;
6
7pub const DEFAULT_URL: &str = "https://gitmoji.dev/api/gitmojis";
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum EmojiFormat {
13 UseCode,
15 UseEmoji,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(default)]
21pub 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 #[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 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 #[must_use]
71 pub const fn auto_add(&self) -> bool {
72 self.auto_add
73 }
74
75 #[must_use]
77 pub const fn format(&self) -> &EmojiFormat {
78 &self.format
79 }
80
81 #[must_use]
83 pub const fn signed(&self) -> bool {
84 self.signed
85 }
86
87 #[must_use]
89 pub const fn scope(&self) -> bool {
90 self.scope
91 }
92
93 #[must_use]
95 pub fn update_url(&self) -> &str {
96 self.update_url.as_ref()
97 }
98
99 pub fn set_update_url(&mut self, update_url: Url) {
101 self.update_url = update_url;
102 }
103
104 #[must_use]
106 pub const fn last_update(&self) -> Option<Timestamp> {
107 self.last_update
108 }
109
110 #[must_use]
112 pub fn gitmojis(&self) -> &[Gitmoji] {
113 self.gitmojis.as_ref()
114 }
115
116 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)]
138pub 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 #[must_use]
150 pub fn auto_add(&self) -> Option<bool> {
151 self.auto_add
152 }
153
154 #[must_use]
156 pub fn format(&self) -> Option<EmojiFormat> {
157 self.format
158 }
159
160 #[must_use]
162 pub fn signed(&self) -> Option<bool> {
163 self.signed
164 }
165
166 #[must_use]
168 pub fn scope(&self) -> Option<bool> {
169 self.scope
170 }
171
172 #[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)]
180pub struct Gitmoji {
182 emoji: String,
183 code: String,
184 name: Option<String>,
185 description: Option<String>,
186}
187
188impl Gitmoji {
189 #[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 #[must_use]
207 pub fn emoji(&self) -> &str {
208 self.emoji.as_ref()
209 }
210
211 #[must_use]
213 pub fn code(&self) -> &str {
214 self.code.as_ref()
215 }
216
217 #[must_use]
219 pub fn name(&self) -> Option<&str> {
220 self.name.as_deref()
221 }
222
223 #[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 let toml = toml_edit::ser::to_string(&gitmoji);
266 let_assert!(Ok(toml) = toml);
267
268 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 let toml = toml_edit::ser::to_string(&config);
287 let_assert!(Ok(toml) = toml);
288
289 let result = toml_edit::de::from_str::<GitmojiConfig>(&toml);
291 let_assert!(Ok(result) = result);
292
293 check!(result == config);
294 }
295}